successfully able to run simple workflows

Signed-off-by: Casey Lee <cplee@nektos.com>
This commit is contained in:
Casey Lee
2020-02-06 22:17:58 -08:00
parent 8c49ba0cec
commit 532af98aef
23 changed files with 958 additions and 495 deletions

View File

@@ -10,12 +10,12 @@ import (
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/nektos/act/pkg/common"
"github.com/pkg/errors"
"golang.org/x/crypto/ssh/terminal"
)
// NewDockerRunExecutorInput the input for the NewDockerRunExecutor function
type NewDockerRunExecutorInput struct {
DockerExecutorInput
Image string
Entrypoint []string
Cmd []string
@@ -30,182 +30,198 @@ type NewDockerRunExecutorInput struct {
// NewDockerRunExecutor function to create a run executor for the container
func NewDockerRunExecutor(input NewDockerRunExecutorInput) common.Executor {
return func() error {
cr := new(containerReference)
cr.input = input
input.Logger.Infof("docker run image=%s entrypoint=%+q cmd=%+q", input.Image, input.Entrypoint, input.Cmd)
if input.Dryrun {
return common.
NewInfoExecutor("docker run image=%s entrypoint=%+q cmd=%+q", input.Image, input.Entrypoint, input.Cmd).
Then(
common.NewPipelineExecutor(
cr.connect(),
cr.find(),
cr.remove().IfBool(!input.ReuseContainers),
cr.create(),
cr.copyContent(),
cr.attach(),
cr.start(),
cr.wait(),
).Finally(
cr.remove().IfBool(!input.ReuseContainers),
).IfNot(common.Dryrun),
)
}
type containerReference struct {
input NewDockerRunExecutorInput
cli *client.Client
id string
}
func (cr *containerReference) connect() common.Executor {
return func(ctx context.Context) error {
cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
return errors.WithStack(err)
}
cli.NegotiateAPIVersion(ctx)
cr.cli = cli
return nil
}
}
func (cr *containerReference) find() common.Executor {
return func(ctx context.Context) error {
containers, err := cr.cli.ContainerList(ctx, types.ContainerListOptions{
All: true,
})
if err != nil {
return errors.WithStack(err)
}
for _, container := range containers {
for _, name := range container.Names {
if name[1:] == cr.input.Name {
cr.id = container.ID
return nil
}
}
}
cr.id = ""
return nil
}
}
func (cr *containerReference) remove() common.Executor {
return func(ctx context.Context) error {
if cr.id == "" {
return nil
}
cli, err := client.NewClientWithOpts(client.FromEnv)
logger := common.Logger(ctx)
err := cr.cli.ContainerRemove(context.Background(), cr.id, types.ContainerRemoveOptions{
RemoveVolumes: true,
Force: true,
})
if err != nil {
return err
return errors.WithStack(err)
}
cli.NegotiateAPIVersion(input.Ctx)
cr.id = ""
// check if container exists
containerID, err := findContainer(input, cli, input.Name)
if err != nil {
return err
}
// if we have an old container and we aren't reusing, remove it!
if !input.ReuseContainers && containerID != "" {
input.Logger.Debugf("Found existing container for %s...removing", input.Name)
removeContainer(input, cli, containerID)
containerID = ""
}
// create a new container if we don't have one to reuse
if containerID == "" {
containerID, err = createContainer(input, cli)
if err != nil {
return err
}
}
// be sure to cleanup container if we aren't reusing
if !input.ReuseContainers {
defer removeContainer(input, cli, containerID)
}
executor := common.NewPipelineExecutor(
func() error {
return copyContentToContainer(input, cli, containerID)
}, func() error {
return attachContainer(input, cli, containerID)
}, func() error {
return startContainer(input, cli, containerID)
}, func() error {
return waitContainer(input, cli, containerID)
},
)
return executor()
}
}
func createContainer(input NewDockerRunExecutorInput, cli *client.Client) (string, error) {
isTerminal := terminal.IsTerminal(int(os.Stdout.Fd()))
config := &container.Config{
Image: input.Image,
Cmd: input.Cmd,
Entrypoint: input.Entrypoint,
WorkingDir: input.WorkingDir,
Env: input.Env,
Tty: isTerminal,
}
if len(input.Volumes) > 0 {
config.Volumes = make(map[string]struct{})
for _, vol := range input.Volumes {
config.Volumes[vol] = struct{}{}
}
}
resp, err := cli.ContainerCreate(input.Ctx, config, &container.HostConfig{
Binds: input.Binds,
}, nil, input.Name)
if err != nil {
return "", err
}
input.Logger.Debugf("Created container name=%s id=%v from image %v", input.Name, resp.ID, input.Image)
input.Logger.Debugf("ENV ==> %v", input.Env)
return resp.ID, nil
}
func findContainer(input NewDockerRunExecutorInput, cli *client.Client, containerName string) (string, error) {
containers, err := cli.ContainerList(input.Ctx, types.ContainerListOptions{
All: true,
})
if err != nil {
return "", err
}
for _, container := range containers {
for _, name := range container.Names {
if name[1:] == containerName {
return container.ID, nil
}
}
}
return "", nil
}
func removeContainer(input NewDockerRunExecutorInput, cli *client.Client, containerID string) {
err := cli.ContainerRemove(context.Background(), containerID, types.ContainerRemoveOptions{
RemoveVolumes: true,
Force: true,
})
if err != nil {
input.Logger.Errorf("%v", err)
}
input.Logger.Debugf("Removed container: %v", containerID)
}
func copyContentToContainer(input NewDockerRunExecutorInput, cli *client.Client, containerID string) error {
for dstPath, srcReader := range input.Content {
input.Logger.Debugf("Extracting content to '%s'", dstPath)
err := cli.CopyToContainer(input.Ctx, containerID, dstPath, srcReader, types.CopyToContainerOptions{})
if err != nil {
return err
}
}
return nil
}
func attachContainer(input NewDockerRunExecutorInput, cli *client.Client, containerID string) error {
out, err := cli.ContainerAttach(input.Ctx, containerID, types.ContainerAttachOptions{
Stream: true,
Stdout: true,
Stderr: true,
})
if err != nil {
return err
}
isTerminal := terminal.IsTerminal(int(os.Stdout.Fd()))
if !isTerminal || os.Getenv("NORAW") != "" {
go input.logDockerOutput(out.Reader)
} else {
go input.streamDockerOutput(out.Reader)
}
return nil
}
func startContainer(input NewDockerRunExecutorInput, cli *client.Client, containerID string) error {
input.Logger.Debugf("STARTING image=%s entrypoint=%s cmd=%v", input.Image, input.Entrypoint, input.Cmd)
if err := cli.ContainerStart(input.Ctx, containerID, types.ContainerStartOptions{}); err != nil {
return err
}
input.Logger.Debugf("Started container: %v", containerID)
return nil
}
func waitContainer(input NewDockerRunExecutorInput, cli *client.Client, containerID string) error {
statusCh, errCh := cli.ContainerWait(input.Ctx, containerID, container.WaitConditionNotRunning)
var statusCode int64
select {
case err := <-errCh:
if err != nil {
return err
}
case status := <-statusCh:
statusCode = status.StatusCode
}
input.Logger.Debugf("Return status: %v", statusCode)
if statusCode == 0 {
logger.Debugf("Removed container: %v", cr.id)
return nil
} else if statusCode == 78 {
return fmt.Errorf("exit with `NEUTRAL`: 78")
}
return fmt.Errorf("exit with `FAILURE`: %v", statusCode)
}
func (cr *containerReference) create() common.Executor {
return func(ctx context.Context) error {
if cr.id != "" {
return nil
}
logger := common.Logger(ctx)
isTerminal := terminal.IsTerminal(int(os.Stdout.Fd()))
input := cr.input
config := &container.Config{
Image: input.Image,
Cmd: input.Cmd,
Entrypoint: input.Entrypoint,
WorkingDir: input.WorkingDir,
Env: input.Env,
Tty: isTerminal,
}
if len(input.Volumes) > 0 {
config.Volumes = make(map[string]struct{})
for _, vol := range input.Volumes {
config.Volumes[vol] = struct{}{}
}
}
resp, err := cr.cli.ContainerCreate(ctx, config, &container.HostConfig{
Binds: input.Binds,
}, nil, input.Name)
if err != nil {
return errors.WithStack(err)
}
logger.Debugf("Created container name=%s id=%v from image %v", input.Name, resp.ID, input.Image)
logger.Debugf("ENV ==> %v", input.Env)
cr.id = resp.ID
return nil
}
}
func (cr *containerReference) copyContent() common.Executor {
return func(ctx context.Context) error {
logger := common.Logger(ctx)
for dstPath, srcReader := range cr.input.Content {
logger.Debugf("Extracting content to '%s'", dstPath)
err := cr.cli.CopyToContainer(ctx, cr.id, dstPath, srcReader, types.CopyToContainerOptions{})
if err != nil {
return errors.WithStack(err)
}
}
return nil
}
}
func (cr *containerReference) attach() common.Executor {
return func(ctx context.Context) error {
out, err := cr.cli.ContainerAttach(ctx, cr.id, types.ContainerAttachOptions{
Stream: true,
Stdout: true,
Stderr: true,
})
if err != nil {
return errors.WithStack(err)
}
isTerminal := terminal.IsTerminal(int(os.Stdout.Fd()))
if !isTerminal || os.Getenv("NORAW") != "" {
go logDockerOutput(ctx, out.Reader)
} else {
go streamDockerOutput(ctx, out.Reader)
}
return nil
}
}
func (cr *containerReference) start() common.Executor {
return func(ctx context.Context) error {
logger := common.Logger(ctx)
logger.Debugf("STARTING image=%s entrypoint=%s cmd=%v", cr.input.Image, cr.input.Entrypoint, cr.input.Cmd)
if err := cr.cli.ContainerStart(ctx, cr.id, types.ContainerStartOptions{}); err != nil {
return errors.WithStack(err)
}
logger.Debugf("Started container: %v", cr.id)
return nil
}
}
func (cr *containerReference) wait() common.Executor {
return func(ctx context.Context) error {
logger := common.Logger(ctx)
statusCh, errCh := cr.cli.ContainerWait(ctx, cr.id, container.WaitConditionNotRunning)
var statusCode int64
select {
case err := <-errCh:
if err != nil {
return errors.WithStack(err)
}
case status := <-statusCh:
statusCode = status.StatusCode
}
logger.Debugf("Return status: %v", statusCode)
if statusCode == 0 {
return nil
} else if statusCode == 78 {
return fmt.Errorf("exit with `NEUTRAL`: 78")
}
return fmt.Errorf("exit with `FAILURE`: %v", statusCode)
}
}