forked from Ivasoft/github-actions
All checks were successful
continuous-integration/drone/push Build is passing
211 lines
5.0 KiB
Go
211 lines
5.0 KiB
Go
package plugin
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"github.com/drone-plugins/drone-github-actions/daemon"
|
|
"github.com/drone-plugins/drone-github-actions/utils"
|
|
"github.com/pkg/errors"
|
|
|
|
docker "github.com/docker/docker/client"
|
|
"github.com/opencontainers/selinux/go-selinux"
|
|
)
|
|
|
|
const (
|
|
envFile = "/tmp/action.env"
|
|
secretFile = "/tmp/action.secrets"
|
|
workflowFile = "/tmp/workflow.yml"
|
|
eventPayloadFile = "/tmp/event.json"
|
|
)
|
|
|
|
var (
|
|
secrets = []string{"GITHUB_TOKEN"}
|
|
)
|
|
|
|
type (
|
|
Action struct {
|
|
Uses string
|
|
With map[string]string
|
|
Env map[string]string
|
|
Image string
|
|
EventPayload string // Webhook event payload
|
|
Actor string
|
|
Verbose bool
|
|
NoForcePull bool
|
|
Kvm bool
|
|
}
|
|
|
|
Plugin struct {
|
|
Action Action
|
|
Daemon daemon.Daemon // Docker daemon configuration
|
|
}
|
|
)
|
|
|
|
// Exec executes the plugin step
|
|
func (p Plugin) Exec() error {
|
|
if err := daemon.StartDaemon(p.Daemon); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := utils.CreateWorkflowFile(workflowFile, p.Action.Uses,
|
|
p.Action.With, p.Action.Env); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := utils.CreateEnvAndSecretFile(envFile, secretFile, secrets); err != nil {
|
|
return err
|
|
}
|
|
|
|
cmdArgs := []string{
|
|
"-W",
|
|
workflowFile,
|
|
"-P",
|
|
fmt.Sprintf("ubuntu-latest=%s", p.Action.Image),
|
|
"--secret-file",
|
|
secretFile,
|
|
"--env-file",
|
|
envFile,
|
|
"-b",
|
|
"--detect-event",
|
|
}
|
|
|
|
// optional arguments
|
|
if p.Action.Actor != "" {
|
|
cmdArgs = append(cmdArgs, "--actor")
|
|
cmdArgs = append(cmdArgs, p.Action.Actor)
|
|
}
|
|
|
|
if p.Action.EventPayload != "" {
|
|
if err := ioutil.WriteFile(eventPayloadFile, []byte(p.Action.EventPayload), 0644); err != nil {
|
|
return errors.Wrap(err, "failed to write event payload to file")
|
|
}
|
|
|
|
cmdArgs = append(cmdArgs, "--eventpath", eventPayloadFile)
|
|
}
|
|
|
|
if p.Action.Verbose {
|
|
cmdArgs = append(cmdArgs, "-v")
|
|
}
|
|
|
|
if p.Action.NoForcePull {
|
|
cmdArgs = append(cmdArgs, "--pull=false")
|
|
}
|
|
|
|
if p.Action.Kvm {
|
|
cmdArgs = append(cmdArgs, "--privileged")
|
|
}
|
|
|
|
if p.Daemon.Disabled {
|
|
hostWorkDirPath, guestWorkDirPath, err := getWorkDirPath(p, context.Background())
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to locate working directory on the host. You may use DinD instead by disabling daemon_off.")
|
|
}
|
|
|
|
bindModifiers := ""
|
|
if runtime.GOOS == "darwin" {
|
|
bindModifiers = ":delegated"
|
|
}
|
|
if selinux.GetEnabled() {
|
|
bindModifiers = ":z"
|
|
}
|
|
opt := fmt.Sprintf("--volume=%s:%s%s", hostWorkDirPath, guestWorkDirPath, bindModifiers)
|
|
if p.Action.Kvm {
|
|
opt = opt + " --volume=/dev/kvm:/dev/kvm"
|
|
}
|
|
cmdArgs = append(cmdArgs, "--container-options", opt)
|
|
}
|
|
|
|
cmd := exec.Command("act", cmdArgs...)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
trace(cmd)
|
|
|
|
err := cmd.Run()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// trace writes each command to stdout with the command wrapped in an xml
|
|
// tag so that it can be extracted and displayed in the logs.
|
|
func trace(cmd *exec.Cmd) {
|
|
fmt.Fprintf(os.Stdout, "+ %s\n", strings.Join(cmd.Args, " "))
|
|
}
|
|
|
|
func getWorkDirPath(p Plugin, ctx context.Context) (string, string, error) {
|
|
// Connect to the docker
|
|
docker, err := docker.NewClient("unix://"+daemon.StdDockerSocketPath, "v1.25", nil, nil)
|
|
if err != nil {
|
|
return "", "", errors.Wrap(err, "failed to create docker client")
|
|
}
|
|
defer docker.Close()
|
|
|
|
// Determine our container identifier
|
|
cntrId, err := getContainerId()
|
|
if err != nil {
|
|
return "", "", errors.Wrap(err, "failed to get our container id")
|
|
}
|
|
|
|
// Find the proper mount
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return "", "", errors.Wrap(err, "failed to locate the working directory in the container")
|
|
}
|
|
|
|
cntr, err := docker.ContainerInspect(ctx, cntrId)
|
|
if err != nil {
|
|
return "", "", errors.Wrap(err, "failed to inspect ourselves as a container")
|
|
}
|
|
for _, i := range cntr.Mounts {
|
|
if i.Destination == cwd {
|
|
return i.Source, i.Destination, nil
|
|
}
|
|
}
|
|
|
|
return "", "", errors.New("mount point with working directory not found")
|
|
}
|
|
|
|
func getContainerId() (string, error) {
|
|
file, err := os.Open("/proc/self/mountinfo")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer file.Close()
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
for scanner.Scan() {
|
|
// The file name is the container identifier
|
|
// Example: 467 457 0:33 /var/lib/docker/containers/3005e3555a74d3196ea35c5a1ae54fe35a49d55102299208df8dc275b5e9309c/hostname /etc/hostname rw,noatime - btrfs /dev/sda3 rw,space_cache=v2,subvolid=5,subvol=/
|
|
// Example: 700 678 8:3 /var/lib/docker/containers/d012398a817e7c659f892bfdda00dd2a416a0443da0b31a4c86567ae4b77a6a7/hostname /etc/hostname rw,noatime - ext4 /dev/sda3 rw,noacl
|
|
line := scanner.Text()
|
|
const prefix = "/docker/containers/"
|
|
pathOffset := strings.Index(line, prefix)
|
|
if pathOffset == -1 {
|
|
continue
|
|
}
|
|
|
|
postPrefix := line[pathOffset+len(prefix):]
|
|
endOffset := strings.Index(postPrefix, "/")
|
|
if endOffset == -1 {
|
|
continue
|
|
}
|
|
|
|
result := postPrefix[:endOffset]
|
|
if result == "" {
|
|
continue
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
return "", errors.New("no suitable cgroup entry found")
|
|
}
|