Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for service containers #1949

Merged
merged 33 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
cbc838d
Support services (#42)
Zettat123 Apr 19, 2023
5cc4bd0
Support services options (#45)
Zettat123 Apr 19, 2023
ca623ce
Support intepolation for `env` of `services` (#47)
Zettat123 Apr 20, 2023
7a09a48
Support services `credentials` (#51)
Zettat123 Apr 25, 2023
92f9cc7
Add ContainerMaxLifetime and ContainerNetworkMode options
GuessWhoSamFoo Aug 7, 2023
bcfbd28
Fix container network issue (#56)
sillyguodong May 16, 2023
a168f8f
Check volumes (#60)
Zettat123 Jun 5, 2023
78e4613
Remove ContainerMaxLifetime; fix lint
GuessWhoSamFoo Aug 7, 2023
2cfb56b
Remove unused ValidVolumes
GuessWhoSamFoo Aug 7, 2023
973a5d8
Remove ConnectToNetwork
GuessWhoSamFoo Aug 12, 2023
197d19d
Add docker stubs
GuessWhoSamFoo Aug 12, 2023
83c1304
Close docker clients to prevent file descriptor leaks
GuessWhoSamFoo Aug 12, 2023
fee22cd
Fix the error when removing network in self-hosted mode (#69)
Zettat123 Jun 28, 2023
b84f831
Move service container and network cleanup to rc.cleanUpJobContainer
GuessWhoSamFoo Aug 12, 2023
d0aad7f
Add --network flag; default to host if not using service containers o…
GuessWhoSamFoo Aug 12, 2023
3711e0d
Correctly close executor to prevent fd leak
GuessWhoSamFoo Aug 13, 2023
7480591
Revert to tail instead of full path
GuessWhoSamFoo Aug 14, 2023
f3b7953
fix network duplication
ChristopherHX Aug 22, 2023
e90c543
backport networkingConfig for aliaes
ChristopherHX Aug 22, 2023
779ad2a
don't hardcode netMode host
ChristopherHX Aug 22, 2023
1e8ce80
Convert services test to table driven tests
ZauberNerd Sep 3, 2023
0f0639c
Add failing tests for services
ZauberNerd Sep 3, 2023
5bad75c
Expose service container ports onto the host
ZauberNerd Sep 3, 2023
5f5f3cf
Set container network mode in artifacts server test to host mode
ZauberNerd Sep 3, 2023
10ab2db
Log container network mode when creating/starting a container
ZauberNerd Sep 3, 2023
5ce86a6
fix: Correctly handle ContainerNetworkMode
ChristopherHX Sep 4, 2023
ce9437f
Merge branch 'master' into service-container
ChristopherHX Sep 4, 2023
ed086f2
fix: missing service container network
ChristopherHX Sep 4, 2023
8e5b4f9
Always remove service containers
ZauberNerd Oct 11, 2023
092a41c
Remove networks only if no active endpoints exist
ZauberNerd Oct 11, 2023
41b68eb
Ensure job containers are stopped before starting a new job
ZauberNerd Oct 13, 2023
0f701ec
fix: go build -tags WITHOUT_DOCKER
ChristopherHX Oct 17, 2023
93089ed
Merge branch 'master' into service-container
ZauberNerd Oct 19, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type Input struct {
matrix []string
actionCachePath string
logPrefixJobID bool
networkName string
}

func (i *Input) resolve(path string) string {
Expand Down
3 changes: 3 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/AlecAivazis/survey/v2"
"github.com/adrg/xdg"
"github.com/andreaskoch/go-fswatch"
docker_container "github.com/docker/docker/api/types/container"
"github.com/joho/godotenv"
gitignore "github.com/sabhiram/go-gitignore"
log "github.com/sirupsen/logrus"
Expand Down Expand Up @@ -96,6 +97,7 @@ func Execute(ctx context.Context, version string) {
rootCmd.PersistentFlags().StringVarP(&input.cacheServerAddr, "cache-server-addr", "", common.GetOutboundIP().String(), "Defines the address to which the cache server binds.")
rootCmd.PersistentFlags().Uint16VarP(&input.cacheServerPort, "cache-server-port", "", 0, "Defines the port where the artifact server listens. 0 means a randomly available port.")
rootCmd.PersistentFlags().StringVarP(&input.actionCachePath, "action-cache-path", "", filepath.Join(CacheHomeDir, "act"), "Defines the path where the actions get cached and host workspaces created.")
rootCmd.PersistentFlags().StringVarP(&input.networkName, "network", "", "host", "Sets a docker network name. Defaults to host.")
rootCmd.SetArgs(args())

if err := rootCmd.Execute(); err != nil {
Expand Down Expand Up @@ -612,6 +614,7 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str
ReplaceGheActionWithGithubCom: input.replaceGheActionWithGithubCom,
ReplaceGheActionTokenWithGithubCom: input.replaceGheActionTokenWithGithubCom,
Matrix: matrixes,
ContainerNetworkMode: docker_container.NetworkMode(input.networkName),
}
r, err := runner.New(config)
if err != nil {
Expand Down
38 changes: 21 additions & 17 deletions pkg/container/container_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,32 @@ import (
"context"
"io"

"github.com/docker/go-connections/nat"
"github.com/nektos/act/pkg/common"
)

// NewContainerInput the input for the New function
type NewContainerInput struct {
Image string
Username string
Password string
Entrypoint []string
Cmd []string
WorkingDir string
Env []string
Binds []string
Mounts map[string]string
Name string
Stdout io.Writer
Stderr io.Writer
NetworkMode string
Privileged bool
UsernsMode string
Platform string
Options string
Image string
Username string
Password string
Entrypoint []string
Cmd []string
WorkingDir string
Env []string
Binds []string
Mounts map[string]string
Name string
Stdout io.Writer
Stderr io.Writer
NetworkMode string
Privileged bool
UsernsMode string
Platform string
Options string
NetworkAliases []string
ExposedPorts nat.PortSet
PortBindings nat.PortMap
}

// FileEntry is a file to copy to a container
Expand Down
79 changes: 79 additions & 0 deletions pkg/container/docker_network.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//go:build !(WITHOUT_DOCKER || !(linux || darwin || windows))

package container

import (
"context"

"github.com/docker/docker/api/types"
"github.com/nektos/act/pkg/common"
)

func NewDockerNetworkCreateExecutor(name string) common.Executor {
return func(ctx context.Context) error {
cli, err := GetDockerClient(ctx)
if err != nil {
return err
}
defer cli.Close()

// Only create the network if it doesn't exist
networks, err := cli.NetworkList(ctx, types.NetworkListOptions{})
if err != nil {
return err
}
common.Logger(ctx).Debugf("%v", networks)
for _, network := range networks {
if network.Name == name {
common.Logger(ctx).Debugf("Network %v exists", name)
return nil
}
}

_, err = cli.NetworkCreate(ctx, name, types.NetworkCreate{
Driver: "bridge",
Scope: "local",
})
if err != nil {
return err
}

return nil
}
}

func NewDockerNetworkRemoveExecutor(name string) common.Executor {
return func(ctx context.Context) error {
cli, err := GetDockerClient(ctx)
if err != nil {
return err
}
defer cli.Close()

// Make shure that all network of the specified name are removed
// cli.NetworkRemove refuses to remove a network if there are duplicates
networks, err := cli.NetworkList(ctx, types.NetworkListOptions{})
if err != nil {
return err
}
common.Logger(ctx).Debugf("%v", networks)
for _, network := range networks {
if network.Name == name {
result, err := cli.NetworkInspect(ctx, network.ID, types.NetworkInspectOptions{})
if err != nil {
return err
}

if len(result.Containers) == 0 {
if err = cli.NetworkRemove(ctx, network.ID); err != nil {
common.Logger(ctx).Debugf("%v", err)
}
} else {
common.Logger(ctx).Debugf("Refusing to remove network %v because it still has active endpoints", name)
}
}
}

return err
}
}
50 changes: 34 additions & 16 deletions pkg/container/docker_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/stdcopy"
specs "github.com/opencontainers/image-spec/specs-go/v1"
Expand Down Expand Up @@ -66,7 +67,7 @@ func supportsContainerImagePlatform(ctx context.Context, cli client.APIClient) b

func (cr *containerReference) Create(capAdd []string, capDrop []string) common.Executor {
return common.
NewInfoExecutor("%sdocker create image=%s platform=%s entrypoint=%+q cmd=%+q", logPrefix, cr.input.Image, cr.input.Platform, cr.input.Entrypoint, cr.input.Cmd).
NewInfoExecutor("%sdocker create image=%s platform=%s entrypoint=%+q cmd=%+q network=%+q", logPrefix, cr.input.Image, cr.input.Platform, cr.input.Entrypoint, cr.input.Cmd, cr.input.NetworkMode).
Then(
common.NewPipelineExecutor(
cr.connect(),
Expand All @@ -78,7 +79,7 @@ func (cr *containerReference) Create(capAdd []string, capDrop []string) common.E

func (cr *containerReference) Start(attach bool) common.Executor {
return common.
NewInfoExecutor("%sdocker run image=%s platform=%s entrypoint=%+q cmd=%+q", logPrefix, cr.input.Image, cr.input.Platform, cr.input.Entrypoint, cr.input.Cmd).
NewInfoExecutor("%sdocker run image=%s platform=%s entrypoint=%+q cmd=%+q network=%+q", logPrefix, cr.input.Image, cr.input.Platform, cr.input.Entrypoint, cr.input.Cmd, cr.input.NetworkMode).
Then(
common.NewPipelineExecutor(
cr.connect(),
Expand Down Expand Up @@ -346,8 +347,8 @@ func (cr *containerReference) mergeContainerConfigs(ctx context.Context, config
}

if len(copts.netMode.Value()) == 0 {
if err = copts.netMode.Set("host"); err != nil {
return nil, nil, fmt.Errorf("Cannot parse networkmode=host. This is an internal error and should not happen: '%w'", err)
if err = copts.netMode.Set(cr.input.NetworkMode); err != nil {
return nil, nil, fmt.Errorf("Cannot parse networkmode=%s. This is an internal error and should not happen: '%w'", cr.input.NetworkMode, err)
}
}

Expand Down Expand Up @@ -391,10 +392,11 @@ func (cr *containerReference) create(capAdd []string, capDrop []string) common.E
input := cr.input

config := &container.Config{
Image: input.Image,
WorkingDir: input.WorkingDir,
Env: input.Env,
Tty: isTerminal,
Image: input.Image,
WorkingDir: input.WorkingDir,
Env: input.Env,
ExposedPorts: input.ExposedPorts,
Tty: isTerminal,
}
logger.Debugf("Common container.Config ==> %+v", config)

Expand Down Expand Up @@ -430,13 +432,14 @@ func (cr *containerReference) create(capAdd []string, capDrop []string) common.E
}

hostConfig := &container.HostConfig{
CapAdd: capAdd,
CapDrop: capDrop,
Binds: input.Binds,
Mounts: mounts,
NetworkMode: container.NetworkMode(input.NetworkMode),
Privileged: input.Privileged,
UsernsMode: container.UsernsMode(input.UsernsMode),
CapAdd: capAdd,
CapDrop: capDrop,
Binds: input.Binds,
Mounts: mounts,
NetworkMode: container.NetworkMode(input.NetworkMode),
Privileged: input.Privileged,
UsernsMode: container.UsernsMode(input.UsernsMode),
PortBindings: input.PortBindings,
}
logger.Debugf("Common container.HostConfig ==> %+v", hostConfig)

Expand All @@ -445,7 +448,22 @@ func (cr *containerReference) create(capAdd []string, capDrop []string) common.E
return err
}

resp, err := cr.cli.ContainerCreate(ctx, config, hostConfig, nil, platSpecs, input.Name)
var networkingConfig *network.NetworkingConfig
logger.Debugf("input.NetworkAliases ==> %v", input.NetworkAliases)
if hostConfig.NetworkMode.IsUserDefined() && len(input.NetworkAliases) > 0 {
endpointConfig := &network.EndpointSettings{
Aliases: input.NetworkAliases,
}
networkingConfig = &network.NetworkingConfig{
EndpointsConfig: map[string]*network.EndpointSettings{
input.NetworkMode: endpointConfig,
},
}
} else {
logger.Debugf("not a use defined config??")
}

resp, err := cr.cli.ContainerCreate(ctx, config, hostConfig, networkingConfig, platSpecs, input.Name)
if err != nil {
return fmt.Errorf("failed to create container: '%w'", err)
}
Expand Down
1 change: 1 addition & 0 deletions pkg/container/docker_run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func TestDocker(t *testing.T) {
ctx := context.Background()
client, err := GetDockerClient(ctx)
assert.NoError(t, err)
defer client.Close()

dockerBuild := NewDockerBuildExecutor(NewDockerBuildExecutorInput{
ContextDir: "testdata",
Expand Down
12 changes: 12 additions & 0 deletions pkg/container/docker_stub.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,15 @@ func NewDockerVolumeRemoveExecutor(volume string, force bool) common.Executor {
return nil
}
}

func NewDockerNetworkCreateExecutor(name string) common.Executor {
return func(ctx context.Context) error {
return nil
}
}

func NewDockerNetworkRemoveExecutor(name string) common.Executor {
return func(ctx context.Context) error {
return nil
}
}
10 changes: 8 additions & 2 deletions pkg/runner/job_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type jobInfo interface {
result(result string)
}

//nolint:contextcheck,gocyclo
func newJobExecutor(info jobInfo, sf stepFactory, rc *RunContext) common.Executor {
steps := make([]common.Executor, 0)
preSteps := make([]common.Executor, 0)
Expand Down Expand Up @@ -87,7 +88,7 @@ func newJobExecutor(info jobInfo, sf stepFactory, rc *RunContext) common.Executo

postExec := useStepLogger(rc, stepModel, stepStagePost, step.post())
if postExecutor != nil {
// run the post exector in reverse order
// run the post executor in reverse order
postExecutor = postExec.Finally(postExecutor)
} else {
postExecutor = postExec
Expand All @@ -101,7 +102,12 @@ func newJobExecutor(info jobInfo, sf stepFactory, rc *RunContext) common.Executo
// always allow 1 min for stopping and removing the runner, even if we were cancelled
ctx, cancel := context.WithTimeout(common.WithLogger(context.Background(), common.Logger(ctx)), time.Minute)
defer cancel()
err = info.stopContainer()(ctx) //nolint:contextcheck

logger := common.Logger(ctx)
logger.Infof("Cleaning up container for job %s", rc.JobName)
if err = info.stopContainer()(ctx); err != nil {
logger.Errorf("Error while stop job container: %v", err)
}
}
setJobResult(ctx, info, rc, jobError == nil)
setJobOutputs(ctx, rc)
Expand Down
Loading