2020-02-07 14:17:58 +08:00
package runner
import (
2022-12-09 19:16:15 +08:00
"archive/tar"
"bufio"
2020-02-07 14:17:58 +08:00
"context"
2022-11-17 05:29:45 +08:00
"crypto/rand"
2023-02-04 03:54:19 +08:00
"crypto/sha256"
2022-11-17 05:29:45 +08:00
"encoding/hex"
2020-02-14 16:41:20 +08:00
"encoding/json"
2022-11-17 05:29:45 +08:00
"errors"
2020-02-07 14:17:58 +08:00
"fmt"
2022-12-09 19:16:15 +08:00
"io"
2020-02-07 14:17:58 +08:00
"os"
2020-02-24 07:01:25 +08:00
"path/filepath"
2020-02-07 14:17:58 +08:00
"regexp"
2020-02-25 02:56:49 +08:00
"runtime"
2020-02-07 14:17:58 +08:00
"strings"
2022-11-18 16:09:51 +08:00
"time"
2020-02-07 14:17:58 +08:00
2022-06-11 05:16:42 +08:00
"github.com/opencontainers/selinux/go-selinux"
2021-11-26 13:18:31 +08:00
2020-02-07 14:17:58 +08:00
"github.com/nektos/act/pkg/common"
2021-03-29 12:08:40 +08:00
"github.com/nektos/act/pkg/container"
2022-05-24 21:36:06 +08:00
"github.com/nektos/act/pkg/exprparser"
2020-02-07 14:17:58 +08:00
"github.com/nektos/act/pkg/model"
)
// RunContext contains info about current job
type RunContext struct {
2022-11-17 05:29:45 +08:00
Name string
Config * Config
Matrix map [ string ] interface { }
Run * model . Run
EventJSON string
Env map [ string ] string
2023-02-04 21:35:13 +08:00
GlobalEnv map [ string ] string // to pass env changes of GITHUB_ENV and set-env correctly, due to dirty Env field
2022-11-17 05:29:45 +08:00
ExtraPath [ ] string
CurrentStep string
StepResults map [ string ] * model . StepResult
2022-12-16 01:08:31 +08:00
IntraActionState map [ string ] map [ string ] string
2022-11-17 05:29:45 +08:00
ExprEval ExpressionEvaluator
JobContainer container . ExecutionsEnvironment
2023-04-19 11:23:28 +08:00
ServiceContainers [ ] container . ExecutionsEnvironment
2022-11-17 05:29:45 +08:00
OutputMappings map [ MappableOutput ] MappableOutput
JobName string
ActionPath string
Parent * RunContext
Masks [ ] string
cleanUpJobContainer common . Executor
2022-12-16 00:45:22 +08:00
caller * caller // job calling this RunContext (reusable workflows)
2022-03-02 16:29:34 +08:00
}
func ( rc * RunContext ) AddMask ( mask string ) {
rc . Masks = append ( rc . Masks , mask )
2021-12-23 03:19:50 +08:00
}
2021-04-03 04:40:44 +08:00
type MappableOutput struct {
StepID string
OutputName string
2020-02-14 16:41:20 +08:00
}
2020-02-27 15:29:43 +08:00
func ( rc * RunContext ) String ( ) string {
2022-12-16 00:45:22 +08:00
name := fmt . Sprintf ( "%s/%s" , rc . Run . Workflow . Name , rc . Name )
if rc . caller != nil {
// prefix the reusable workflow with the caller job
// this is required to create unique container names
name = fmt . Sprintf ( "%s/%s" , rc . caller . runContext . Run . JobID , name )
}
return name
2020-02-27 15:29:43 +08:00
}
2020-02-07 14:17:58 +08:00
// GetEnv returns the env for the context
func ( rc * RunContext ) GetEnv ( ) map [ string ] string {
if rc . Env == nil {
2022-11-17 05:29:45 +08:00
rc . Env = map [ string ] string { }
if rc . Run != nil && rc . Run . Workflow != nil && rc . Config != nil {
job := rc . Run . Job ( )
if job != nil {
rc . Env = mergeMaps ( rc . Run . Workflow . Env , job . Environment ( ) , rc . Config . Env )
}
}
2020-02-07 14:17:58 +08:00
}
2020-11-18 23:14:34 +08:00
rc . Env [ "ACT" ] = "true"
2023-06-13 11:46:26 +08:00
if ! rc . Config . NoSkipCheckout {
rc . Env [ "ACT_SKIP_CHECKOUT" ] = "true"
}
2020-02-07 14:17:58 +08:00
return rc . Env
}
2020-02-24 07:01:25 +08:00
func ( rc * RunContext ) jobContainerName ( ) string {
2022-11-24 14:45:32 +08:00
return createSimpleContainerName ( rc . Config . ContainerNamePrefix , "WORKFLOW-" + rc . Run . Workflow . Name , "JOB-" + rc . Name )
2020-02-07 14:17:58 +08:00
}
Fix container network issue (#56)
Follow: https://gitea.com/gitea/act_runner/pulls/184
Close https://gitea.com/gitea/act_runner/issues/177
#### changes:
- `act` create new networks only if the value of `NeedCreateNetwork` is true, and remove these networks at last. `NeedCreateNetwork` is passed by `act_runner`. 'NeedCreateNetwork' is true only if `container.network` in the configuration file of the `act_runner` is empty.
- In the `docker create` phase, specify the network to which containers will connect. Because, if not specify , container will connect to `bridge` network which is created automatically by Docker.
- If the network is user defined network ( the value of `container.network` is empty or `<custom-network>`. Because, the network created by `act` is also user defined network.), will also specify alias by `--network-alias`. The alias of service is `<service-id>`. So we can be access service container by `<service-id>:<port>` in the steps of job.
- Won't try to `docker network connect ` network after `docker start` any more.
- Because on the one hand, `docker network connect` applies only to user defined networks, if try to `docker network connect host <container-name>` will return error.
- On the other hand, we just specify network in the stage of `docker create`, the same effect can be achieved.
- Won't try to remove containers and networks berfore the stage of `docker start`, because the name of these containers and netwoks won't be repeat.
Co-authored-by: Jason Song <i@wolfogre.com>
Reviewed-on: https://gitea.com/gitea/act/pulls/56
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: sillyguodong <gedong_1994@163.com>
Co-committed-by: sillyguodong <gedong_1994@163.com>
2023-05-16 14:03:55 +08:00
// networkName return the name of the network which will be created by `act` automatically for job,
// only create network if `rc.Config.ContainerNetworkMode` is empty string.
func ( rc * RunContext ) networkName ( ) string {
return fmt . Sprintf ( "%s-network" , rc . jobContainerName ( ) )
}
2023-04-26 00:31:17 +08:00
func getDockerDaemonSocketMountPath ( daemonPath string ) string {
if protoIndex := strings . Index ( daemonPath , "://" ) ; protoIndex != - 1 {
scheme := daemonPath [ : protoIndex ]
if strings . EqualFold ( scheme , "npipe" ) {
// linux container mount on windows, use the default socket path of the VM / wsl2
return "/var/run/docker.sock"
} else if strings . EqualFold ( scheme , "unix" ) {
return daemonPath [ protoIndex + 3 : ]
} else if strings . IndexFunc ( scheme , func ( r rune ) bool {
return ( r < 'a' || r > 'z' ) && ( r < 'A' || r > 'Z' )
} ) == - 1 {
// unknown protocol use default
return "/var/run/docker.sock"
}
}
return daemonPath
}
2021-05-05 05:50:35 +08:00
// Returns the binds and mounts for the container, resolving paths as appopriate
func ( rc * RunContext ) GetBindsAndMounts ( ) ( [ ] string , map [ string ] string ) {
name := rc . jobContainerName ( )
2021-05-23 22:43:09 +08:00
if rc . Config . ContainerDaemonSocket == "" {
rc . Config . ContainerDaemonSocket = "/var/run/docker.sock"
}
2023-04-26 00:31:17 +08:00
binds := [ ] string { }
if rc . Config . ContainerDaemonSocket != "-" {
daemonPath := getDockerDaemonSocketMountPath ( rc . Config . ContainerDaemonSocket )
binds = append ( binds , fmt . Sprintf ( "%s:%s" , daemonPath , "/var/run/docker.sock" ) )
2021-05-05 05:50:35 +08:00
}
2022-11-17 05:29:45 +08:00
ext := container . LinuxContainerEnvironmentExtensions { }
2021-05-05 05:50:35 +08:00
mounts := map [ string ] string {
"act-toolcache" : "/toolcache" ,
2022-11-17 05:29:45 +08:00
name + "-env" : ext . GetActPath ( ) ,
2021-05-05 05:50:35 +08:00
}
2022-04-05 02:01:13 +08:00
if job := rc . Run . Job ( ) ; job != nil {
if container := job . Container ( ) ; container != nil {
for _ , v := range container . Volumes {
if ! strings . Contains ( v , ":" ) || filepath . IsAbs ( v ) {
// Bind anonymous volume or host file.
binds = append ( binds , v )
} else {
// Mount existing volume.
paths := strings . SplitN ( v , ":" , 2 )
mounts [ paths [ 0 ] ] = paths [ 1 ]
}
}
}
}
2021-05-05 05:50:35 +08:00
if rc . Config . BindWorkdir {
bindModifiers := ""
if runtime . GOOS == "darwin" {
bindModifiers = ":delegated"
}
2021-11-26 13:18:31 +08:00
if selinux . GetEnabled ( ) {
bindModifiers = ":z"
}
2022-11-17 05:29:45 +08:00
binds = append ( binds , fmt . Sprintf ( "%s:%s%s" , rc . Config . Workdir , ext . ToContainerPath ( rc . Config . Workdir ) , bindModifiers ) )
2021-05-05 05:50:35 +08:00
} else {
2022-11-17 05:29:45 +08:00
mounts [ name ] = ext . ToContainerPath ( rc . Config . Workdir )
2021-05-05 05:50:35 +08:00
}
2023-06-05 17:21:59 +08:00
// For Gitea
// add some default binds and mounts to ValidVolumes
rc . Config . ValidVolumes = append ( rc . Config . ValidVolumes , "act-toolcache" )
rc . Config . ValidVolumes = append ( rc . Config . ValidVolumes , name )
rc . Config . ValidVolumes = append ( rc . Config . ValidVolumes , name + "-env" )
// TODO: add a new configuration to control whether the docker daemon can be mounted
rc . Config . ValidVolumes = append ( rc . Config . ValidVolumes , getDockerDaemonSocketMountPath ( rc . Config . ContainerDaemonSocket ) )
2021-05-05 05:50:35 +08:00
return binds , mounts
}
2022-11-17 05:29:45 +08:00
func ( rc * RunContext ) startHostEnvironment ( ) common . Executor {
return func ( ctx context . Context ) error {
logger := common . Logger ( ctx )
rawLogger := logger . WithField ( "raw_output" , true )
logWriter := common . NewLineWriter ( rc . commandHandler ( ctx ) , func ( s string ) bool {
if rc . Config . LogOutput {
rawLogger . Infof ( "%s" , s )
} else {
rawLogger . Debugf ( "%s" , s )
}
return true
} )
cacheDir := rc . ActionCacheDir ( )
randBytes := make ( [ ] byte , 8 )
_ , _ = rand . Read ( randBytes )
miscpath := filepath . Join ( cacheDir , hex . EncodeToString ( randBytes ) )
actPath := filepath . Join ( miscpath , "act" )
2023-02-16 23:34:51 +08:00
if err := os . MkdirAll ( actPath , 0 o777 ) ; err != nil {
2022-11-17 05:29:45 +08:00
return err
}
path := filepath . Join ( miscpath , "hostexecutor" )
2023-02-16 23:34:51 +08:00
if err := os . MkdirAll ( path , 0 o777 ) ; err != nil {
2022-11-17 05:29:45 +08:00
return err
}
runnerTmp := filepath . Join ( miscpath , "tmp" )
2023-02-16 23:34:51 +08:00
if err := os . MkdirAll ( runnerTmp , 0 o777 ) ; err != nil {
2022-11-17 05:29:45 +08:00
return err
}
toolCache := filepath . Join ( cacheDir , "tool_cache" )
rc . JobContainer = & container . HostEnvironment {
Path : path ,
TmpDir : runnerTmp ,
ToolCache : toolCache ,
Workdir : rc . Config . Workdir ,
ActPath : actPath ,
CleanUp : func ( ) {
os . RemoveAll ( miscpath )
} ,
StdOut : logWriter ,
}
rc . cleanUpJobContainer = rc . JobContainer . Remove ( )
2022-12-19 22:58:55 +08:00
for k , v := range rc . JobContainer . GetRunnerContext ( ctx ) {
if v , ok := v . ( string ) ; ok {
rc . Env [ fmt . Sprintf ( "RUNNER_%s" , strings . ToUpper ( k ) ) ] = v
}
}
2022-11-17 05:29:45 +08:00
for _ , env := range os . Environ ( ) {
2023-02-16 23:16:46 +08:00
if k , v , ok := strings . Cut ( env , "=" ) ; ok {
// don't override
if _ , ok := rc . Env [ k ] ; ! ok {
rc . Env [ k ] = v
}
2022-11-17 05:29:45 +08:00
}
}
return common . NewPipelineExecutor (
rc . JobContainer . Copy ( rc . JobContainer . GetActPath ( ) + "/" , & container . FileEntry {
Name : "workflow/event.json" ,
2023-02-16 23:34:51 +08:00
Mode : 0 o644 ,
2022-11-17 05:29:45 +08:00
Body : rc . EventJSON ,
} , & container . FileEntry {
Name : "workflow/envs.txt" ,
2023-02-16 23:34:51 +08:00
Mode : 0 o666 ,
2022-11-17 05:29:45 +08:00
Body : "" ,
} ) ,
) ( ctx )
}
}
2020-02-24 07:01:25 +08:00
func ( rc * RunContext ) startJobContainer ( ) common . Executor {
return func ( ctx context . Context ) error {
2022-06-17 23:55:21 +08:00
logger := common . Logger ( ctx )
image := rc . platformImage ( ctx )
rawLogger := logger . WithField ( "raw_output" , true )
2020-02-25 04:48:12 +08:00
logWriter := common . NewLineWriter ( rc . commandHandler ( ctx ) , func ( s string ) bool {
2020-02-24 07:01:25 +08:00
if rc . Config . LogOutput {
2020-06-24 22:05:05 +08:00
rawLogger . Infof ( "%s" , s )
2020-02-24 07:01:25 +08:00
} else {
2020-06-24 22:05:05 +08:00
rawLogger . Debugf ( "%s" , s )
2020-02-24 07:01:25 +08:00
}
2020-02-25 04:48:12 +08:00
return true
2020-02-24 07:01:25 +08:00
} )
2022-06-17 23:55:21 +08:00
username , password , err := rc . handleCredentials ( ctx )
2021-11-28 02:05:56 +08:00
if err != nil {
return fmt . Errorf ( "failed to handle credentials: %s" , err )
}
2022-06-17 23:55:21 +08:00
logger . Infof ( "\U0001f680 Start image=%s" , image )
2020-02-24 07:01:25 +08:00
name := rc . jobContainerName ( )
2023-06-06 08:21:31 +08:00
// For gitea, to support --volumes-from <container_name_or_id> in options.
// We need to set the container name to the environment variable.
rc . Env [ "JOB_CONTAINER_NAME" ] = name
2020-02-24 07:01:25 +08:00
2020-02-25 02:56:49 +08:00
envList := make ( [ ] string , 0 )
2020-02-26 00:52:05 +08:00
envList = append ( envList , fmt . Sprintf ( "%s=%s" , "RUNNER_TOOL_CACHE" , "/opt/hostedtoolcache" ) )
2020-04-23 14:57:36 +08:00
envList = append ( envList , fmt . Sprintf ( "%s=%s" , "RUNNER_OS" , "Linux" ) )
2022-08-29 23:39:31 +08:00
envList = append ( envList , fmt . Sprintf ( "%s=%s" , "RUNNER_ARCH" , container . RunnerArch ( ctx ) ) )
2020-04-23 23:18:36 +08:00
envList = append ( envList , fmt . Sprintf ( "%s=%s" , "RUNNER_TEMP" , "/tmp" ) )
2022-12-06 18:36:39 +08:00
envList = append ( envList , fmt . Sprintf ( "%s=%s" , "LANG" , "C.UTF-8" ) ) // Use same locale as GitHub Actions
2020-02-25 02:56:49 +08:00
2022-11-17 05:29:45 +08:00
ext := container . LinuxContainerEnvironmentExtensions { }
2021-05-05 05:50:35 +08:00
binds , mounts := rc . GetBindsAndMounts ( )
2020-02-25 09:48:21 +08:00
Fix container network issue (#56)
Follow: https://gitea.com/gitea/act_runner/pulls/184
Close https://gitea.com/gitea/act_runner/issues/177
#### changes:
- `act` create new networks only if the value of `NeedCreateNetwork` is true, and remove these networks at last. `NeedCreateNetwork` is passed by `act_runner`. 'NeedCreateNetwork' is true only if `container.network` in the configuration file of the `act_runner` is empty.
- In the `docker create` phase, specify the network to which containers will connect. Because, if not specify , container will connect to `bridge` network which is created automatically by Docker.
- If the network is user defined network ( the value of `container.network` is empty or `<custom-network>`. Because, the network created by `act` is also user defined network.), will also specify alias by `--network-alias`. The alias of service is `<service-id>`. So we can be access service container by `<service-id>:<port>` in the steps of job.
- Won't try to `docker network connect ` network after `docker start` any more.
- Because on the one hand, `docker network connect` applies only to user defined networks, if try to `docker network connect host <container-name>` will return error.
- On the other hand, we just specify network in the stage of `docker create`, the same effect can be achieved.
- Won't try to remove containers and networks berfore the stage of `docker start`, because the name of these containers and netwoks won't be repeat.
Co-authored-by: Jason Song <i@wolfogre.com>
Reviewed-on: https://gitea.com/gitea/act/pulls/56
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: sillyguodong <gedong_1994@163.com>
Co-committed-by: sillyguodong <gedong_1994@163.com>
2023-05-16 14:03:55 +08:00
// specify the network to which the container will connect when `docker create` stage. (like execute command line: docker create --network <networkName> <image>)
networkName := string ( rc . Config . ContainerNetworkMode )
if networkName == "" {
// if networkName is empty string, will create a new network for the containers.
// and it will be removed after at last.
networkName = rc . networkName ( )
}
2023-04-19 11:23:28 +08:00
// add service containers
Fix container network issue (#56)
Follow: https://gitea.com/gitea/act_runner/pulls/184
Close https://gitea.com/gitea/act_runner/issues/177
#### changes:
- `act` create new networks only if the value of `NeedCreateNetwork` is true, and remove these networks at last. `NeedCreateNetwork` is passed by `act_runner`. 'NeedCreateNetwork' is true only if `container.network` in the configuration file of the `act_runner` is empty.
- In the `docker create` phase, specify the network to which containers will connect. Because, if not specify , container will connect to `bridge` network which is created automatically by Docker.
- If the network is user defined network ( the value of `container.network` is empty or `<custom-network>`. Because, the network created by `act` is also user defined network.), will also specify alias by `--network-alias`. The alias of service is `<service-id>`. So we can be access service container by `<service-id>:<port>` in the steps of job.
- Won't try to `docker network connect ` network after `docker start` any more.
- Because on the one hand, `docker network connect` applies only to user defined networks, if try to `docker network connect host <container-name>` will return error.
- On the other hand, we just specify network in the stage of `docker create`, the same effect can be achieved.
- Won't try to remove containers and networks berfore the stage of `docker start`, because the name of these containers and netwoks won't be repeat.
Co-authored-by: Jason Song <i@wolfogre.com>
Reviewed-on: https://gitea.com/gitea/act/pulls/56
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: sillyguodong <gedong_1994@163.com>
Co-committed-by: sillyguodong <gedong_1994@163.com>
2023-05-16 14:03:55 +08:00
for serviceId , spec := range rc . Run . Job ( ) . Services {
2023-04-23 14:55:17 +08:00
// interpolate env
2023-04-20 16:24:31 +08:00
interpolatedEnvs := make ( map [ string ] string , len ( spec . Env ) )
2023-04-19 11:23:28 +08:00
for k , v := range spec . Env {
2023-04-20 16:24:31 +08:00
interpolatedEnvs [ k ] = rc . ExprEval . Interpolate ( ctx , v )
}
envs := make ( [ ] string , 0 , len ( interpolatedEnvs ) )
for k , v := range interpolatedEnvs {
envs = append ( envs , fmt . Sprintf ( "%s=%s" , k , v ) )
2023-04-19 11:23:28 +08:00
}
2023-04-23 14:55:17 +08:00
// interpolate cmd
interpolatedCmd := make ( [ ] string , 0 , len ( spec . Cmd ) )
for _ , v := range spec . Cmd {
interpolatedCmd = append ( interpolatedCmd , rc . ExprEval . Interpolate ( ctx , v ) )
}
2023-04-25 14:45:39 +08:00
username , password , err := rc . handleServiceCredentials ( ctx , spec . Credentials )
if err != nil {
Fix container network issue (#56)
Follow: https://gitea.com/gitea/act_runner/pulls/184
Close https://gitea.com/gitea/act_runner/issues/177
#### changes:
- `act` create new networks only if the value of `NeedCreateNetwork` is true, and remove these networks at last. `NeedCreateNetwork` is passed by `act_runner`. 'NeedCreateNetwork' is true only if `container.network` in the configuration file of the `act_runner` is empty.
- In the `docker create` phase, specify the network to which containers will connect. Because, if not specify , container will connect to `bridge` network which is created automatically by Docker.
- If the network is user defined network ( the value of `container.network` is empty or `<custom-network>`. Because, the network created by `act` is also user defined network.), will also specify alias by `--network-alias`. The alias of service is `<service-id>`. So we can be access service container by `<service-id>:<port>` in the steps of job.
- Won't try to `docker network connect ` network after `docker start` any more.
- Because on the one hand, `docker network connect` applies only to user defined networks, if try to `docker network connect host <container-name>` will return error.
- On the other hand, we just specify network in the stage of `docker create`, the same effect can be achieved.
- Won't try to remove containers and networks berfore the stage of `docker start`, because the name of these containers and netwoks won't be repeat.
Co-authored-by: Jason Song <i@wolfogre.com>
Reviewed-on: https://gitea.com/gitea/act/pulls/56
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: sillyguodong <gedong_1994@163.com>
Co-committed-by: sillyguodong <gedong_1994@163.com>
2023-05-16 14:03:55 +08:00
return fmt . Errorf ( "failed to handle service %s credentials: %w" , serviceId , err )
2023-04-25 14:45:39 +08:00
}
2023-06-05 17:21:59 +08:00
serviceBinds , serviceMounts := rc . GetServiceBindsAndMounts ( spec . Volumes )
Fix container network issue (#56)
Follow: https://gitea.com/gitea/act_runner/pulls/184
Close https://gitea.com/gitea/act_runner/issues/177
#### changes:
- `act` create new networks only if the value of `NeedCreateNetwork` is true, and remove these networks at last. `NeedCreateNetwork` is passed by `act_runner`. 'NeedCreateNetwork' is true only if `container.network` in the configuration file of the `act_runner` is empty.
- In the `docker create` phase, specify the network to which containers will connect. Because, if not specify , container will connect to `bridge` network which is created automatically by Docker.
- If the network is user defined network ( the value of `container.network` is empty or `<custom-network>`. Because, the network created by `act` is also user defined network.), will also specify alias by `--network-alias`. The alias of service is `<service-id>`. So we can be access service container by `<service-id>:<port>` in the steps of job.
- Won't try to `docker network connect ` network after `docker start` any more.
- Because on the one hand, `docker network connect` applies only to user defined networks, if try to `docker network connect host <container-name>` will return error.
- On the other hand, we just specify network in the stage of `docker create`, the same effect can be achieved.
- Won't try to remove containers and networks berfore the stage of `docker start`, because the name of these containers and netwoks won't be repeat.
Co-authored-by: Jason Song <i@wolfogre.com>
Reviewed-on: https://gitea.com/gitea/act/pulls/56
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: sillyguodong <gedong_1994@163.com>
Co-committed-by: sillyguodong <gedong_1994@163.com>
2023-05-16 14:03:55 +08:00
serviceContainerName := createSimpleContainerName ( rc . jobContainerName ( ) , serviceId )
2023-04-19 11:23:28 +08:00
c := container . NewContainer ( & container . NewContainerInput {
2023-06-05 17:21:59 +08:00
Name : serviceContainerName ,
WorkingDir : ext . ToContainerPath ( rc . Config . Workdir ) ,
Image : spec . Image ,
Username : username ,
Password : password ,
Cmd : interpolatedCmd ,
Env : envs ,
Mounts : serviceMounts ,
Binds : serviceBinds ,
2023-04-19 11:23:28 +08:00
Stdout : logWriter ,
Stderr : logWriter ,
Privileged : rc . Config . Privileged ,
UsernsMode : rc . Config . UsernsMode ,
Platform : rc . Config . ContainerArchitecture ,
AutoRemove : rc . Config . AutoRemove ,
2023-04-19 21:53:57 +08:00
Options : spec . Options ,
Fix container network issue (#56)
Follow: https://gitea.com/gitea/act_runner/pulls/184
Close https://gitea.com/gitea/act_runner/issues/177
#### changes:
- `act` create new networks only if the value of `NeedCreateNetwork` is true, and remove these networks at last. `NeedCreateNetwork` is passed by `act_runner`. 'NeedCreateNetwork' is true only if `container.network` in the configuration file of the `act_runner` is empty.
- In the `docker create` phase, specify the network to which containers will connect. Because, if not specify , container will connect to `bridge` network which is created automatically by Docker.
- If the network is user defined network ( the value of `container.network` is empty or `<custom-network>`. Because, the network created by `act` is also user defined network.), will also specify alias by `--network-alias`. The alias of service is `<service-id>`. So we can be access service container by `<service-id>:<port>` in the steps of job.
- Won't try to `docker network connect ` network after `docker start` any more.
- Because on the one hand, `docker network connect` applies only to user defined networks, if try to `docker network connect host <container-name>` will return error.
- On the other hand, we just specify network in the stage of `docker create`, the same effect can be achieved.
- Won't try to remove containers and networks berfore the stage of `docker start`, because the name of these containers and netwoks won't be repeat.
Co-authored-by: Jason Song <i@wolfogre.com>
Reviewed-on: https://gitea.com/gitea/act/pulls/56
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: sillyguodong <gedong_1994@163.com>
Co-committed-by: sillyguodong <gedong_1994@163.com>
2023-05-16 14:03:55 +08:00
NetworkMode : networkName ,
NetworkAliases : [ ] string { serviceId } ,
2023-06-05 17:21:59 +08:00
ValidVolumes : rc . Config . ValidVolumes ,
2023-04-19 11:23:28 +08:00
} )
rc . ServiceContainers = append ( rc . ServiceContainers , c )
}
2022-11-17 05:29:45 +08:00
rc . cleanUpJobContainer = func ( ctx context . Context ) error {
if rc . JobContainer != nil && ! rc . Config . ReuseContainers {
return rc . JobContainer . Remove ( ) .
Then ( container . NewDockerVolumeRemoveExecutor ( rc . jobContainerName ( ) , false ) ) .
Then ( container . NewDockerVolumeRemoveExecutor ( rc . jobContainerName ( ) + "-env" , false ) ) ( ctx )
}
return nil
}
2020-02-24 07:01:25 +08:00
rc . JobContainer = container . NewContainer ( & container . NewContainerInput {
Fix container network issue (#56)
Follow: https://gitea.com/gitea/act_runner/pulls/184
Close https://gitea.com/gitea/act_runner/issues/177
#### changes:
- `act` create new networks only if the value of `NeedCreateNetwork` is true, and remove these networks at last. `NeedCreateNetwork` is passed by `act_runner`. 'NeedCreateNetwork' is true only if `container.network` in the configuration file of the `act_runner` is empty.
- In the `docker create` phase, specify the network to which containers will connect. Because, if not specify , container will connect to `bridge` network which is created automatically by Docker.
- If the network is user defined network ( the value of `container.network` is empty or `<custom-network>`. Because, the network created by `act` is also user defined network.), will also specify alias by `--network-alias`. The alias of service is `<service-id>`. So we can be access service container by `<service-id>:<port>` in the steps of job.
- Won't try to `docker network connect ` network after `docker start` any more.
- Because on the one hand, `docker network connect` applies only to user defined networks, if try to `docker network connect host <container-name>` will return error.
- On the other hand, we just specify network in the stage of `docker create`, the same effect can be achieved.
- Won't try to remove containers and networks berfore the stage of `docker start`, because the name of these containers and netwoks won't be repeat.
Co-authored-by: Jason Song <i@wolfogre.com>
Reviewed-on: https://gitea.com/gitea/act/pulls/56
Reviewed-by: Jason Song <i@wolfogre.com>
Co-authored-by: sillyguodong <gedong_1994@163.com>
Co-committed-by: sillyguodong <gedong_1994@163.com>
2023-05-16 14:03:55 +08:00
Cmd : nil ,
Entrypoint : [ ] string { "/bin/sleep" , fmt . Sprint ( rc . Config . ContainerMaxLifetime . Round ( time . Second ) . Seconds ( ) ) } ,
WorkingDir : ext . ToContainerPath ( rc . Config . Workdir ) ,
Image : image ,
Username : username ,
Password : password ,
Name : name ,
Env : envList ,
Mounts : mounts ,
NetworkMode : networkName ,
NetworkAliases : [ ] string { rc . Name } ,
Binds : binds ,
Stdout : logWriter ,
Stderr : logWriter ,
Privileged : rc . Config . Privileged ,
UsernsMode : rc . Config . UsernsMode ,
Platform : rc . Config . ContainerArchitecture ,
Options : rc . options ( ctx ) ,
AutoRemove : rc . Config . AutoRemove ,
2023-06-05 17:21:59 +08:00
ValidVolumes : rc . Config . ValidVolumes ,
2020-02-24 07:01:25 +08:00
} )
2022-11-17 05:29:45 +08:00
if rc . JobContainer == nil {
return errors . New ( "Failed to create job container" )
}
2020-02-24 07:01:25 +08:00
return common . NewPipelineExecutor (
2023-04-19 11:23:28 +08:00
rc . pullServicesImages ( rc . Config . ForcePull ) ,
2020-02-24 07:01:25 +08:00
rc . JobContainer . Pull ( rc . Config . ForcePull ) ,
2023-06-28 10:27:12 +08:00
container . NewDockerNetworkCreateExecutor ( networkName ) . IfBool ( ! rc . IsHostEnv ( ctx ) && rc . Config . ContainerNetworkMode == "" ) , // if the value of `ContainerNetworkMode` is empty string, then will create a new network for containers.
2023-04-19 11:23:28 +08:00
rc . startServiceContainers ( networkName ) ,
2021-06-05 00:06:59 +08:00
rc . JobContainer . Create ( rc . Config . ContainerCapAdd , rc . Config . ContainerCapDrop ) ,
2020-02-24 07:01:25 +08:00
rc . JobContainer . Start ( false ) ,
2022-11-17 05:29:45 +08:00
rc . JobContainer . Copy ( rc . JobContainer . GetActPath ( ) + "/" , & container . FileEntry {
2020-02-24 07:01:25 +08:00
Name : "workflow/event.json" ,
2023-02-16 23:34:51 +08:00
Mode : 0 o644 ,
2020-02-24 07:01:25 +08:00
Body : rc . EventJSON ,
2021-01-12 14:39:43 +08:00
} , & container . FileEntry {
Name : "workflow/envs.txt" ,
2023-02-16 23:34:51 +08:00
Mode : 0 o666 ,
2021-05-06 21:30:12 +08:00
Body : "" ,
2020-02-24 07:01:25 +08:00
} ) ,
) ( ctx )
}
}
2021-08-11 03:40:20 +08:00
func ( rc * RunContext ) execJobContainer ( cmd [ ] string , env map [ string ] string , user , workdir string ) common . Executor {
2020-02-24 07:01:25 +08:00
return func ( ctx context . Context ) error {
2021-08-11 03:40:20 +08:00
return rc . JobContainer . Exec ( cmd , env , user , workdir ) ( ctx )
2020-02-24 07:01:25 +08:00
}
}
2020-06-18 23:21:55 +08:00
2023-02-04 21:35:13 +08:00
func ( rc * RunContext ) ApplyExtraPath ( ctx context . Context , env * map [ string ] string ) {
2022-12-09 19:16:15 +08:00
if rc . ExtraPath != nil && len ( rc . ExtraPath ) > 0 {
path := rc . JobContainer . GetPathVariableName ( )
2023-04-19 02:09:57 +08:00
if rc . JobContainer . IsEnvironmentCaseInsensitive ( ) {
// On windows system Path and PATH could also be in the map
for k := range * env {
if strings . EqualFold ( path , k ) {
path = k
break
}
}
}
2022-12-09 19:16:15 +08:00
if ( * env ) [ path ] == "" {
2023-02-04 21:35:13 +08:00
cenv := map [ string ] string { }
var cpath string
if err := rc . JobContainer . UpdateFromImageEnv ( & cenv ) ( ctx ) ; err == nil {
if p , ok := cenv [ path ] ; ok {
cpath = p
}
}
if len ( cpath ) == 0 {
cpath = rc . JobContainer . DefaultPathVariable ( )
}
( * env ) [ path ] = cpath
2022-12-09 19:16:15 +08:00
}
( * env ) [ path ] = rc . JobContainer . JoinPathVariable ( append ( rc . ExtraPath , ( * env ) [ path ] ) ... )
}
}
func ( rc * RunContext ) UpdateExtraPath ( ctx context . Context , githubEnvPath string ) error {
if common . Dryrun ( ctx ) {
return nil
}
pathTar , err := rc . JobContainer . GetContainerArchive ( ctx , githubEnvPath )
if err != nil {
return err
}
defer pathTar . Close ( )
reader := tar . NewReader ( pathTar )
_ , err = reader . Next ( )
if err != nil && err != io . EOF {
return err
}
s := bufio . NewScanner ( reader )
for s . Scan ( ) {
line := s . Text ( )
if len ( line ) > 0 {
rc . addPath ( ctx , line )
}
}
return nil
}
2020-06-18 23:21:55 +08:00
// stopJobContainer removes the job container (if it exists) and its volume (if it exists) if !rc.Config.ReuseContainers
2020-02-24 07:01:25 +08:00
func ( rc * RunContext ) stopJobContainer ( ) common . Executor {
return func ( ctx context . Context ) error {
2022-11-17 05:29:45 +08:00
if rc . cleanUpJobContainer != nil && ! rc . Config . ReuseContainers {
return rc . cleanUpJobContainer ( ctx )
2020-02-24 07:01:25 +08:00
}
return nil
}
}
2023-04-19 11:23:28 +08:00
func ( rc * RunContext ) pullServicesImages ( forcePull bool ) common . Executor {
return func ( ctx context . Context ) error {
execs := [ ] common . Executor { }
for _ , c := range rc . ServiceContainers {
execs = append ( execs , c . Pull ( forcePull ) )
}
return common . NewParallelExecutor ( len ( execs ) , execs ... ) ( ctx )
}
}
func ( rc * RunContext ) startServiceContainers ( networkName string ) common . Executor {
return func ( ctx context . Context ) error {
execs := [ ] common . Executor { }
for _ , c := range rc . ServiceContainers {
execs = append ( execs , common . NewPipelineExecutor (
c . Pull ( false ) ,
c . Create ( rc . Config . ContainerCapAdd , rc . Config . ContainerCapDrop ) ,
c . Start ( false ) ,
) )
}
return common . NewParallelExecutor ( len ( execs ) , execs ... ) ( ctx )
}
}
func ( rc * RunContext ) stopServiceContainers ( ) common . Executor {
return func ( ctx context . Context ) error {
execs := [ ] common . Executor { }
for _ , c := range rc . ServiceContainers {
execs = append ( execs , c . Remove ( ) )
}
return common . NewParallelExecutor ( len ( execs ) , execs ... ) ( ctx )
}
}
2021-05-05 05:50:35 +08:00
// Prepare the mounts and binds for the worker
2020-02-25 02:56:49 +08:00
// ActionCacheDir is for rc
func ( rc * RunContext ) ActionCacheDir ( ) string {
2023-06-16 11:41:39 +08:00
if rc . Config . ActionCacheDir != "" {
return rc . Config . ActionCacheDir
}
2020-02-24 14:34:48 +08:00
var xdgCache string
var ok bool
2021-04-05 23:51:13 +08:00
if xdgCache , ok = os . LookupEnv ( "XDG_CACHE_HOME" ) ; ! ok || xdgCache == "" {
2023-03-31 21:08:46 +08:00
if home , err := os . UserHomeDir ( ) ; err == nil {
2021-04-05 23:51:13 +08:00
xdgCache = filepath . Join ( home , ".cache" )
} else if xdgCache , err = filepath . Abs ( "." ) ; err != nil {
2023-04-18 22:17:36 +08:00
// It's almost impossible to get here, so the temp dir is a good fallback
xdgCache = os . TempDir ( )
2020-02-24 14:34:48 +08:00
}
}
return filepath . Join ( xdgCache , "act" )
}
2021-11-24 23:49:08 +08:00
// Interpolate outputs after a job is done
func ( rc * RunContext ) interpolateOutputs ( ) common . Executor {
return func ( ctx context . Context ) error {
2022-06-17 23:55:21 +08:00
ee := rc . NewExpressionEvaluator ( ctx )
2021-11-24 23:49:08 +08:00
for k , v := range rc . Run . Job ( ) . Outputs {
2022-06-17 23:55:21 +08:00
interpolated := ee . Interpolate ( ctx , v )
2021-11-24 23:49:08 +08:00
if v != interpolated {
rc . Run . Job ( ) . Outputs [ k ] = interpolated
}
}
return nil
}
}
2022-02-09 01:22:41 +08:00
func ( rc * RunContext ) startContainer ( ) common . Executor {
2022-11-17 05:29:45 +08:00
return func ( ctx context . Context ) error {
2023-03-14 22:07:31 +08:00
if rc . IsHostEnv ( ctx ) {
2022-11-17 05:29:45 +08:00
return rc . startHostEnvironment ( ) ( ctx )
}
return rc . startJobContainer ( ) ( ctx )
}
2022-02-09 01:22:41 +08:00
}
2020-02-27 15:29:43 +08:00
2023-03-14 22:07:31 +08:00
func ( rc * RunContext ) IsHostEnv ( ctx context . Context ) bool {
2023-05-04 01:49:17 +08:00
platform := rc . runsOnImage ( ctx )
image := rc . containerImage ( ctx )
return image == "" && strings . EqualFold ( platform , "-self-hosted" )
2023-03-14 22:07:31 +08:00
}
2022-02-09 01:22:41 +08:00
func ( rc * RunContext ) stopContainer ( ) common . Executor {
return rc . stopJobContainer ( )
}
2020-02-07 14:17:58 +08:00
2022-02-09 01:22:41 +08:00
func ( rc * RunContext ) closeContainer ( ) common . Executor {
return func ( ctx context . Context ) error {
if rc . JobContainer != nil {
return rc . JobContainer . Close ( ) ( ctx )
2020-02-11 07:27:05 +08:00
}
2022-02-09 01:22:41 +08:00
return nil
2020-02-24 07:01:25 +08:00
}
2022-02-09 01:22:41 +08:00
}
2021-12-09 04:57:42 +08:00
2022-02-09 01:22:41 +08:00
func ( rc * RunContext ) matrix ( ) map [ string ] interface { } {
return rc . Matrix
}
2021-12-09 04:57:42 +08:00
2022-02-09 01:22:41 +08:00
func ( rc * RunContext ) result ( result string ) {
rc . Run . Job ( ) . Result = result
}
2020-02-18 02:11:16 +08:00
2022-02-09 01:22:41 +08:00
func ( rc * RunContext ) steps ( ) [ ] * model . Step {
return rc . Run . Job ( ) . Steps
}
// Executor returns a pipeline executor for all the steps in the job
2023-07-11 12:27:43 +08:00
func ( rc * RunContext ) Executor ( ) ( common . Executor , error ) {
2022-12-16 00:45:22 +08:00
var executor common . Executor
2023-07-11 12:27:43 +08:00
var jobType , err = rc . Run . Job ( ) . Type ( )
2022-12-16 00:45:22 +08:00
2023-07-11 12:27:43 +08:00
switch jobType {
2022-12-16 00:45:22 +08:00
case model . JobTypeDefault :
executor = newJobExecutor ( rc , & stepFactoryImpl { } , rc )
case model . JobTypeReusableWorkflowLocal :
executor = newLocalReusableWorkflowExecutor ( rc )
case model . JobTypeReusableWorkflowRemote :
executor = newRemoteReusableWorkflowExecutor ( rc )
2023-07-11 12:27:43 +08:00
case model . JobTypeInvalid :
return nil , err
2022-12-16 00:45:22 +08:00
}
2022-02-26 02:39:50 +08:00
return func ( ctx context . Context ) error {
2022-12-16 00:45:22 +08:00
res , err := rc . isEnabled ( ctx )
2022-02-26 02:39:50 +08:00
if err != nil {
return err
}
2022-12-16 00:45:22 +08:00
if res {
return executor ( ctx )
2022-02-26 02:39:50 +08:00
}
return nil
2023-07-11 12:27:43 +08:00
} , nil
2020-02-24 07:01:25 +08:00
}
2020-02-18 02:11:16 +08:00
2023-05-04 01:49:17 +08:00
func ( rc * RunContext ) containerImage ( ctx context . Context ) string {
2020-03-17 05:58:10 +08:00
job := rc . Run . Job ( )
c := job . Container ( )
if c != nil {
2022-06-17 23:55:21 +08:00
return rc . ExprEval . Interpolate ( ctx , c . Image )
2020-03-17 05:58:10 +08:00
}
2023-05-04 01:49:17 +08:00
return ""
}
func ( rc * RunContext ) runsOnImage ( ctx context . Context ) string {
job := rc . Run . Job ( )
2021-01-21 22:02:48 +08:00
if job . RunsOn ( ) == nil {
2022-06-17 23:55:21 +08:00
common . Logger ( ctx ) . Errorf ( "'runs-on' key not defined in %s" , rc . String ( ) )
2021-01-21 22:02:48 +08:00
}
2022-11-22 16:39:19 +08:00
runsOn := job . RunsOn ( )
for i , v := range runsOn {
runsOn [ i ] = rc . ExprEval . Interpolate ( ctx , v )
}
if pick := rc . Config . PlatformPicker ; pick != nil {
if image := pick ( runsOn ) ; image != "" {
return image
}
}
for _ , runnerLabel := range runsOn {
image := rc . Config . Platforms [ strings . ToLower ( runnerLabel ) ]
2020-03-17 05:58:10 +08:00
if image != "" {
return image
}
}
return ""
}
2023-05-04 01:49:17 +08:00
func ( rc * RunContext ) platformImage ( ctx context . Context ) string {
if containerImage := rc . containerImage ( ctx ) ; containerImage != "" {
return containerImage
}
return rc . runsOnImage ( ctx )
}
2023-09-12 21:35:25 +08:00
func ( rc * RunContext ) options ( ctx context . Context ) string {
2021-09-10 13:03:40 +08:00
job := rc . Run . Job ( )
c := job . Container ( )
2023-09-12 21:35:25 +08:00
if c != nil {
return rc . ExprEval . Interpolate ( ctx , c . Options )
2021-09-10 13:03:40 +08:00
}
2023-09-12 21:35:25 +08:00
return rc . Config . ContainerOptions
2021-09-10 13:03:40 +08:00
}
2022-02-26 02:39:50 +08:00
func ( rc * RunContext ) isEnabled ( ctx context . Context ) ( bool , error ) {
2020-02-24 07:01:25 +08:00
job := rc . Run . Job ( )
2020-05-05 03:18:13 +08:00
l := common . Logger ( ctx )
2023-07-11 12:27:43 +08:00
runJob , runJobErr := EvalBool ( ctx , rc . ExprEval , job . If . Value , exprparser . DefaultStatusCheckSuccess )
jobType , jobTypeErr := job . Type ( )
if runJobErr != nil {
return false , fmt . Errorf ( " \u274C Error in if-expression: \"if: %s\" (%s)" , job . If . Value , runJobErr )
}
if jobType == model . JobTypeInvalid {
return false , jobTypeErr
} else if jobType != model . JobTypeDefault {
return true , nil
2020-11-18 01:31:05 +08:00
}
2023-07-11 12:27:43 +08:00
2020-11-18 01:31:05 +08:00
if ! runJob {
2022-06-17 23:55:21 +08:00
l . WithField ( "jobResult" , "skipped" ) . Debugf ( "Skipping job '%s' due to '%s'" , job . Name , job . If . Value )
2022-02-26 02:39:50 +08:00
return false , nil
2020-02-24 07:01:25 +08:00
}
2020-02-21 11:43:20 +08:00
2022-06-17 23:55:21 +08:00
img := rc . platformImage ( ctx )
2020-03-17 05:58:10 +08:00
if img == "" {
2021-01-21 22:02:48 +08:00
if job . RunsOn ( ) == nil {
2022-06-17 23:55:21 +08:00
l . Errorf ( "'runs-on' key not defined in %s" , rc . String ( ) )
2021-01-21 22:02:48 +08:00
}
2020-12-09 02:13:07 +08:00
for _ , runnerLabel := range job . RunsOn ( ) {
2022-06-17 23:55:21 +08:00
platformName := rc . ExprEval . Interpolate ( ctx , runnerLabel )
2021-09-14 07:14:41 +08:00
l . Infof ( "\U0001F6A7 Skipping unsupported platform -- Try running with `-P %+v=...`" , platformName )
2020-12-09 02:13:07 +08:00
}
2022-02-26 02:39:50 +08:00
return false , nil
2020-02-18 02:11:16 +08:00
}
2022-02-26 02:39:50 +08:00
return true , nil
2020-02-18 02:11:16 +08:00
}
2020-02-07 14:17:58 +08:00
func mergeMaps ( maps ... map [ string ] string ) map [ string ] string {
rtnMap := make ( map [ string ] string )
for _ , m := range maps {
for k , v := range m {
rtnMap [ k ] = v
}
}
return rtnMap
}
2022-11-24 14:45:32 +08:00
// deprecated: use createSimpleContainerName
2020-02-24 07:01:25 +08:00
func createContainerName ( parts ... string ) string {
2023-02-04 03:54:19 +08:00
name := strings . Join ( parts , "-" )
2020-02-24 07:01:25 +08:00
pattern := regexp . MustCompile ( "[^a-zA-Z0-9]" )
2023-02-04 03:54:19 +08:00
name = pattern . ReplaceAllString ( name , "-" )
name = strings . ReplaceAll ( name , "--" , "-" )
hash := sha256 . Sum256 ( [ ] byte ( name ) )
// SHA256 is 64 hex characters. So trim name to 63 characters to make room for the hash and separator
trimmedName := strings . Trim ( trimToLen ( name , 63 ) , "-" )
return fmt . Sprintf ( "%s-%x" , trimmedName , hash )
2020-02-07 14:17:58 +08:00
}
2022-11-24 14:45:32 +08:00
func createSimpleContainerName ( parts ... string ) string {
pattern := regexp . MustCompile ( "[^a-zA-Z0-9-]" )
name := make ( [ ] string , 0 , len ( parts ) )
for _ , v := range parts {
v = pattern . ReplaceAllString ( v , "-" )
v = strings . Trim ( v , "-" )
for strings . Contains ( v , "--" ) {
v = strings . ReplaceAll ( v , "--" , "-" )
}
if v != "" {
name = append ( name , v )
}
}
return strings . Join ( name , "_" )
}
2020-02-07 14:17:58 +08:00
func trimToLen ( s string , l int ) string {
2020-02-21 11:43:20 +08:00
if l < 0 {
l = 0
}
2020-02-07 14:17:58 +08:00
if len ( s ) > l {
return s [ : l ]
}
return s
}
2020-02-14 16:41:20 +08:00
2021-12-23 03:52:09 +08:00
func ( rc * RunContext ) getJobContext ( ) * model . JobContext {
2020-02-14 16:41:20 +08:00
jobStatus := "success"
for _ , stepStatus := range rc . StepResults {
2021-12-23 03:52:09 +08:00
if stepStatus . Conclusion == model . StepStatusFailure {
2020-02-14 16:41:20 +08:00
jobStatus = "failure"
break
}
}
2021-12-23 03:52:09 +08:00
return & model . JobContext {
2020-02-14 16:41:20 +08:00
Status : jobStatus ,
}
}
2021-12-23 03:52:09 +08:00
func ( rc * RunContext ) getStepsContext ( ) map [ string ] * model . StepResult {
2020-02-14 16:41:20 +08:00
return rc . StepResults
}
2022-06-17 23:55:21 +08:00
func ( rc * RunContext ) getGithubContext ( ctx context . Context ) * model . GithubContext {
logger := common . Logger ( ctx )
2021-12-23 03:52:09 +08:00
ghc := & model . GithubContext {
2021-05-07 04:02:29 +08:00
Event : make ( map [ string ] interface { } ) ,
Workflow : rc . Run . Workflow . Name ,
RunID : rc . Config . Env [ "GITHUB_RUN_ID" ] ,
RunNumber : rc . Config . Env [ "GITHUB_RUN_NUMBER" ] ,
Actor : rc . Config . Actor ,
EventName : rc . Config . EventName ,
Action : rc . CurrentStep ,
2022-05-12 03:06:05 +08:00
Token : rc . Config . Token ,
2023-02-28 03:10:31 +08:00
Job : rc . Run . JobID ,
2021-12-23 03:19:50 +08:00
ActionPath : rc . ActionPath ,
2023-10-04 07:13:05 +08:00
ActionRepository : rc . Env [ "GITHUB_ACTION_REPOSITORY" ] ,
ActionRef : rc . Env [ "GITHUB_ACTION_REF" ] ,
2021-05-07 04:02:29 +08:00
RepositoryOwner : rc . Config . Env [ "GITHUB_REPOSITORY_OWNER" ] ,
RetentionDays : rc . Config . Env [ "GITHUB_RETENTION_DAYS" ] ,
RunnerPerflog : rc . Config . Env [ "RUNNER_PERFLOG" ] ,
RunnerTrackingID : rc . Config . Env [ "RUNNER_TRACKING_ID" ] ,
2023-02-04 03:35:49 +08:00
Repository : rc . Config . Env [ "GITHUB_REPOSITORY" ] ,
Ref : rc . Config . Env [ "GITHUB_REF" ] ,
Sha : rc . Config . Env [ "SHA_REF" ] ,
RefName : rc . Config . Env [ "GITHUB_REF_NAME" ] ,
RefType : rc . Config . Env [ "GITHUB_REF_TYPE" ] ,
BaseRef : rc . Config . Env [ "GITHUB_BASE_REF" ] ,
HeadRef : rc . Config . Env [ "GITHUB_HEAD_REF" ] ,
Workspace : rc . Config . Env [ "GITHUB_WORKSPACE" ] ,
2020-03-07 06:17:57 +08:00
}
2022-11-17 05:29:45 +08:00
if rc . JobContainer != nil {
ghc . EventPath = rc . JobContainer . GetActPath ( ) + "/workflow/event.json"
ghc . Workspace = rc . JobContainer . ToContainerPath ( rc . Config . Workdir )
}
2021-05-06 07:11:43 +08:00
2021-05-07 04:02:29 +08:00
if ghc . RunID == "" {
ghc . RunID = "1"
2020-09-23 05:13:29 +08:00
}
2021-05-06 07:11:43 +08:00
2021-05-07 04:02:29 +08:00
if ghc . RunNumber == "" {
ghc . RunNumber = "1"
2020-09-23 05:13:29 +08:00
}
2021-05-06 07:11:43 +08:00
2021-05-07 04:02:29 +08:00
if ghc . RetentionDays == "" {
ghc . RetentionDays = "0"
}
if ghc . RunnerPerflog == "" {
ghc . RunnerPerflog = "/dev/null"
2020-02-14 16:41:20 +08:00
}
2020-05-12 15:14:56 +08:00
// Backwards compatibility for configs that require
// a default rather than being run as a cmd
if ghc . Actor == "" {
ghc . Actor = "nektos/act"
}
2023-03-16 11:45:29 +08:00
{ // Adapt to Gitea
if preset := rc . Config . PresetGitHubContext ; preset != nil {
ghc . Event = preset . Event
ghc . RunID = preset . RunID
ghc . RunNumber = preset . RunNumber
ghc . Actor = preset . Actor
ghc . Repository = preset . Repository
ghc . EventName = preset . EventName
ghc . Sha = preset . Sha
ghc . Ref = preset . Ref
ghc . RefName = preset . RefName
ghc . RefType = preset . RefType
ghc . HeadRef = preset . HeadRef
ghc . BaseRef = preset . BaseRef
ghc . Token = preset . Token
ghc . RepositoryOwner = preset . RepositoryOwner
ghc . RetentionDays = preset . RetentionDays
2023-06-16 13:12:43 +08:00
instance := rc . Config . GitHubInstance
if ! strings . HasPrefix ( instance , "http://" ) &&
! strings . HasPrefix ( instance , "https://" ) {
instance = "https://" + instance
}
ghc . ServerURL = instance
ghc . APIURL = instance + "/api/v1" // the version of Gitea is v1
ghc . GraphQLURL = "" // Gitea doesn't support graphql
2023-03-16 11:45:29 +08:00
return ghc
2021-05-07 04:02:29 +08:00
}
2020-02-14 16:41:20 +08:00
}
2020-05-05 03:18:13 +08:00
if rc . EventJSON != "" {
2023-02-04 03:35:49 +08:00
err := json . Unmarshal ( [ ] byte ( rc . EventJSON ) , & ghc . Event )
2020-05-05 03:18:13 +08:00
if err != nil {
2022-06-17 23:55:21 +08:00
logger . Errorf ( "Unable to Unmarshal event '%s': %v" , rc . EventJSON , err )
2020-05-05 03:18:13 +08:00
}
2020-02-14 16:41:20 +08:00
}
2020-03-07 06:17:57 +08:00
2023-02-04 03:35:49 +08:00
ghc . SetBaseAndHeadRef ( )
repoPath := rc . Config . Workdir
ghc . SetRepositoryAndOwner ( ctx , rc . Config . GitHubInstance , rc . Config . RemoteName , repoPath )
if ghc . Ref == "" {
ghc . SetRef ( ctx , rc . Config . DefaultBranch , repoPath )
2020-03-07 06:17:57 +08:00
}
2023-02-04 03:35:49 +08:00
if ghc . Sha == "" {
ghc . SetSha ( ctx , repoPath )
2022-05-08 22:23:19 +08:00
}
2023-02-04 03:35:49 +08:00
ghc . SetRefTypeAndName ( )
2023-04-13 21:09:28 +08:00
// defaults
ghc . ServerURL = "https://github.com"
ghc . APIURL = "https://api.github.com"
ghc . GraphQLURL = "https://api.github.com/graphql"
// per GHES
if rc . Config . GitHubInstance != "github.com" {
ghc . ServerURL = fmt . Sprintf ( "https://%s" , rc . Config . GitHubInstance )
ghc . APIURL = fmt . Sprintf ( "https://%s/api/v3" , rc . Config . GitHubInstance )
ghc . GraphQLURL = fmt . Sprintf ( "https://%s/api/graphql" , rc . Config . GitHubInstance )
}
2023-06-20 15:36:10 +08:00
{ // Adapt to Gitea
instance := rc . Config . GitHubInstance
if ! strings . HasPrefix ( instance , "http://" ) &&
! strings . HasPrefix ( instance , "https://" ) {
instance = "https://" + instance
}
ghc . ServerURL = instance
ghc . APIURL = instance + "/api/v1" // the version of Gitea is v1
ghc . GraphQLURL = "" // Gitea doesn't support graphql
}
2023-04-13 21:09:28 +08:00
// allow to be overridden by user
if rc . Config . Env [ "GITHUB_SERVER_URL" ] != "" {
ghc . ServerURL = rc . Config . Env [ "GITHUB_SERVER_URL" ]
}
if rc . Config . Env [ "GITHUB_API_URL" ] != "" {
2023-04-13 22:09:29 +08:00
ghc . APIURL = rc . Config . Env [ "GITHUB_API_URL" ]
2023-04-13 21:09:28 +08:00
}
if rc . Config . Env [ "GITHUB_GRAPHQL_URL" ] != "" {
2023-04-13 22:09:29 +08:00
ghc . GraphQLURL = rc . Config . Env [ "GITHUB_GRAPHQL_URL" ]
2023-04-13 21:09:28 +08:00
}
2020-02-14 16:41:20 +08:00
return ghc
}
2021-12-23 03:52:09 +08:00
func isLocalCheckout ( ghc * model . GithubContext , step * model . Step ) bool {
2021-05-10 23:12:57 +08:00
if step . Type ( ) == model . StepTypeInvalid {
2021-05-05 05:50:35 +08:00
// This will be errored out by the executor later, we need this here to avoid a null panic though
return false
}
2020-03-10 08:45:42 +08:00
if step . Type ( ) != model . StepTypeUsesActionRemote {
return false
}
remoteAction := newRemoteAction ( step . Uses )
2021-05-10 23:12:57 +08:00
if remoteAction == nil {
// IsCheckout() will nil panic if we dont bail out early
return false
}
2020-03-10 08:45:42 +08:00
if ! remoteAction . IsCheckout ( ) {
return false
}
if repository , ok := step . With [ "repository" ] ; ok && repository != ghc . Repository {
return false
}
if repository , ok := step . With [ "ref" ] ; ok && repository != ghc . Ref {
return false
}
return true
}
2020-03-07 06:17:57 +08:00
func nestedMapLookup ( m map [ string ] interface { } , ks ... string ) ( rval interface { } ) {
var ok bool
if len ( ks ) == 0 { // degenerate input
return nil
}
if rval , ok = m [ ks [ 0 ] ] ; ! ok {
return nil
} else if len ( ks ) == 1 { // we've reached the final key
return rval
} else if m , ok = rval . ( map [ string ] interface { } ) ; ! ok {
return nil
} else { // 1+ more keys
return nestedMapLookup ( m , ks [ 1 : ] ... )
}
}
2022-10-07 05:58:16 +08:00
func ( rc * RunContext ) withGithubEnv ( ctx context . Context , github * model . GithubContext , env map [ string ] string ) map [ string ] string {
2020-09-28 23:22:42 +08:00
env [ "CI" ] = "true"
2020-02-14 16:41:20 +08:00
env [ "GITHUB_WORKFLOW" ] = github . Workflow
env [ "GITHUB_RUN_ID" ] = github . RunID
env [ "GITHUB_RUN_NUMBER" ] = github . RunNumber
env [ "GITHUB_ACTION" ] = github . Action
2021-12-23 03:19:50 +08:00
env [ "GITHUB_ACTION_PATH" ] = github . ActionPath
env [ "GITHUB_ACTION_REPOSITORY" ] = github . ActionRepository
env [ "GITHUB_ACTION_REF" ] = github . ActionRef
2020-02-25 02:56:49 +08:00
env [ "GITHUB_ACTIONS" ] = "true"
2020-02-14 16:41:20 +08:00
env [ "GITHUB_ACTOR" ] = github . Actor
env [ "GITHUB_REPOSITORY" ] = github . Repository
env [ "GITHUB_EVENT_NAME" ] = github . EventName
env [ "GITHUB_EVENT_PATH" ] = github . EventPath
env [ "GITHUB_WORKSPACE" ] = github . Workspace
env [ "GITHUB_SHA" ] = github . Sha
env [ "GITHUB_REF" ] = github . Ref
2022-05-08 22:23:19 +08:00
env [ "GITHUB_REF_NAME" ] = github . RefName
env [ "GITHUB_REF_TYPE" ] = github . RefType
2020-03-10 08:45:42 +08:00
env [ "GITHUB_TOKEN" ] = github . Token
2023-02-28 03:10:31 +08:00
env [ "GITHUB_JOB" ] = github . Job
2021-05-07 04:02:29 +08:00
env [ "GITHUB_REPOSITORY_OWNER" ] = github . RepositoryOwner
env [ "GITHUB_RETENTION_DAYS" ] = github . RetentionDays
env [ "RUNNER_PERFLOG" ] = github . RunnerPerflog
env [ "RUNNER_TRACKING_ID" ] = github . RunnerTrackingID
2023-02-04 03:35:49 +08:00
env [ "GITHUB_BASE_REF" ] = github . BaseRef
env [ "GITHUB_HEAD_REF" ] = github . HeadRef
2023-04-13 21:09:28 +08:00
env [ "GITHUB_SERVER_URL" ] = github . ServerURL
env [ "GITHUB_API_URL" ] = github . APIURL
env [ "GITHUB_GRAPHQL_URL" ] = github . GraphQLURL
2023-02-04 03:35:49 +08:00
2023-03-16 11:45:29 +08:00
{ // Adapt to Gitea
instance := rc . Config . GitHubInstance
if ! strings . HasPrefix ( instance , "http://" ) &&
! strings . HasPrefix ( instance , "https://" ) {
instance = "https://" + instance
2022-11-16 18:00:45 +08:00
}
2023-05-04 17:45:53 +08:00
env [ "GITHUB_SERVER_URL" ] = instance
env [ "GITHUB_API_URL" ] = instance + "/api/v1" // the version of Gitea is v1
env [ "GITHUB_GRAPHQL_URL" ] = "" // Gitea doesn't support graphql
2021-05-06 00:42:34 +08:00
}
2021-03-18 08:14:08 +08:00
2021-11-11 01:57:22 +08:00
if rc . Config . ArtifactServerPath != "" {
setActionRuntimeVars ( rc , env )
}
2021-03-18 08:14:08 +08:00
job := rc . Run . Job ( )
2023-08-09 20:41:12 +08:00
for _ , runnerLabel := range job . RunsOn ( ) {
platformName := rc . ExprEval . Interpolate ( ctx , runnerLabel )
if platformName != "" {
if platformName == "ubuntu-latest" {
// hardcode current ubuntu-latest since we have no way to check that 'on the fly'
env [ "ImageOS" ] = "ubuntu20"
} else {
platformName = strings . SplitN ( strings . Replace ( platformName , ` - ` , ` ` , 1 ) , ` . ` , 2 ) [ 0 ]
env [ "ImageOS" ] = platformName
2021-03-18 08:14:08 +08:00
}
}
}
2020-02-14 16:41:20 +08:00
return env
}
2020-03-10 08:45:42 +08:00
2021-11-11 01:57:22 +08:00
func setActionRuntimeVars ( rc * RunContext , env map [ string ] string ) {
actionsRuntimeURL := os . Getenv ( "ACTIONS_RUNTIME_URL" )
if actionsRuntimeURL == "" {
2023-01-16 22:12:20 +08:00
actionsRuntimeURL = fmt . Sprintf ( "http://%s:%s/" , rc . Config . ArtifactServerAddr , rc . Config . ArtifactServerPort )
2021-11-11 01:57:22 +08:00
}
env [ "ACTIONS_RUNTIME_URL" ] = actionsRuntimeURL
actionsRuntimeToken := os . Getenv ( "ACTIONS_RUNTIME_TOKEN" )
if actionsRuntimeToken == "" {
actionsRuntimeToken = "token"
}
env [ "ACTIONS_RUNTIME_TOKEN" ] = actionsRuntimeToken
}
2023-07-11 08:12:12 +08:00
func ( rc * RunContext ) handleCredentials ( ctx context . Context ) ( string , string , error ) {
2021-11-28 02:05:56 +08:00
// TODO: remove below 2 lines when we can release act with breaking changes
2023-07-11 08:12:12 +08:00
username := rc . Config . Secrets [ "DOCKER_USERNAME" ]
password := rc . Config . Secrets [ "DOCKER_PASSWORD" ]
2021-11-28 02:05:56 +08:00
container := rc . Run . Job ( ) . Container ( )
if container == nil || container . Credentials == nil {
2023-07-11 08:12:12 +08:00
return username , password , nil
2021-11-28 02:05:56 +08:00
}
if container . Credentials != nil && len ( container . Credentials ) != 2 {
2023-07-11 08:12:12 +08:00
err := fmt . Errorf ( "invalid property count for key 'credentials:'" )
return "" , "" , err
2021-11-28 02:05:56 +08:00
}
2022-06-17 23:55:21 +08:00
ee := rc . NewExpressionEvaluator ( ctx )
if username = ee . Interpolate ( ctx , container . Credentials [ "username" ] ) ; username == "" {
2023-07-11 08:12:12 +08:00
err := fmt . Errorf ( "failed to interpolate container.credentials.username" )
return "" , "" , err
2021-11-28 02:05:56 +08:00
}
2022-06-17 23:55:21 +08:00
if password = ee . Interpolate ( ctx , container . Credentials [ "password" ] ) ; password == "" {
2023-07-11 08:12:12 +08:00
err := fmt . Errorf ( "failed to interpolate container.credentials.password" )
return "" , "" , err
2021-11-28 02:05:56 +08:00
}
if container . Credentials [ "username" ] == "" || container . Credentials [ "password" ] == "" {
2023-07-11 08:12:12 +08:00
err := fmt . Errorf ( "container.credentials cannot be empty" )
return "" , "" , err
2021-11-28 02:05:56 +08:00
}
2023-07-11 08:12:12 +08:00
return username , password , nil
2021-11-28 02:05:56 +08:00
}
2023-04-25 14:45:39 +08:00
func ( rc * RunContext ) handleServiceCredentials ( ctx context . Context , creds map [ string ] string ) ( username , password string , err error ) {
if creds == nil {
return
}
if len ( creds ) != 2 {
err = fmt . Errorf ( "invalid property count for key 'credentials:'" )
return
}
ee := rc . NewExpressionEvaluator ( ctx )
if username = ee . Interpolate ( ctx , creds [ "username" ] ) ; username == "" {
err = fmt . Errorf ( "failed to interpolate credentials.username" )
return
}
if password = ee . Interpolate ( ctx , creds [ "password" ] ) ; password == "" {
err = fmt . Errorf ( "failed to interpolate credentials.password" )
return
}
return
}
2023-06-05 17:21:59 +08:00
// GetServiceBindsAndMounts returns the binds and mounts for the service container, resolving paths as appopriate
func ( rc * RunContext ) GetServiceBindsAndMounts ( svcVolumes [ ] string ) ( [ ] string , map [ string ] string ) {
if rc . Config . ContainerDaemonSocket == "" {
rc . Config . ContainerDaemonSocket = "/var/run/docker.sock"
}
binds := [ ] string { }
if rc . Config . ContainerDaemonSocket != "-" {
daemonPath := getDockerDaemonSocketMountPath ( rc . Config . ContainerDaemonSocket )
binds = append ( binds , fmt . Sprintf ( "%s:%s" , daemonPath , "/var/run/docker.sock" ) )
}
mounts := map [ string ] string { }
for _ , v := range svcVolumes {
if ! strings . Contains ( v , ":" ) || filepath . IsAbs ( v ) {
// Bind anonymous volume or host file.
binds = append ( binds , v )
} else {
// Mount existing volume.
paths := strings . SplitN ( v , ":" , 2 )
mounts [ paths [ 0 ] ] = paths [ 1 ]
}
}
return binds , mounts
}