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") }