woodpecker/pipeline/backend/ssh/ssh.go
6543 3cd78c9409
Refactor agent (#2021)
- code cleanup
- init backend engine only once
- pass a taskUUID to the backend

---
*Sponsored by Kithara Software GmbH*
2023-07-20 20:39:20 +02:00

153 lines
3.8 KiB
Go

package ssh
import (
"context"
"io"
"strings"
"github.com/melbahja/goph"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
"github.com/woodpecker-ci/woodpecker/pipeline/backend/common"
"github.com/woodpecker-ci/woodpecker/pipeline/backend/types"
"github.com/woodpecker-ci/woodpecker/shared/constant"
)
type ssh struct {
cmd *goph.Cmd
output io.ReadCloser
client *goph.Client
workingdir string
}
type readCloser struct {
io.Reader
}
func (c readCloser) Close() error {
return nil
}
// New returns a new ssh Engine.
func New() types.Engine {
return &ssh{}
}
func (e *ssh) Name() string {
return "ssh"
}
func (e *ssh) IsAvailable(ctx context.Context) bool {
c, ok := ctx.Value(types.CliContext).(*cli.Context)
return ok && c.String("backend-ssh-address") != "" && c.String("backend-ssh-user") != "" && (c.String("backend-ssh-key") != "" || c.String("backend-ssh-password") != "")
}
func (e *ssh) Load(ctx context.Context) error {
cmd, err := e.client.Command("/bin/env", "mktemp", "-d", "-p", "/tmp", "woodpecker-ssh-XXXXXXXXXX")
if err != nil {
return err
}
dir, err := cmd.Output()
if err != nil {
return err
}
e.workingdir = string(dir)
c, ok := ctx.Value(types.CliContext).(*cli.Context)
if !ok {
return types.ErrNoCliContextFound
}
address := c.String("backend-ssh-address")
user := c.String("backend-ssh-user")
var auth goph.Auth
if file := c.String("backend-ssh-key"); file != "" {
keyAuth, err := goph.Key(file, c.String("backend-ssh-key-password"))
if err != nil {
return err
}
auth = append(auth, keyAuth...)
}
if password := c.String("backend-ssh-password"); password != "" {
auth = append(auth, goph.Password(password)...)
}
client, err := goph.New(user, address, auth)
if err != nil {
return err
}
e.client = client
return nil
}
// SetupWorkflow create the workflow environment.
func (e *ssh) SetupWorkflow(context.Context, *types.Config, string) error {
return nil
}
// StartStep start the step.
func (e *ssh) StartStep(ctx context.Context, step *types.Step, taskUUID string) error {
log.Trace().Str("taskUUID", taskUUID).Msgf("Start step %s", step.Name)
// Get environment variables
var command []string
for a, b := range step.Environment {
if a != "HOME" && a != "SHELL" { // Don't override $HOME and $SHELL
command = append(command, a+"="+b)
}
}
if step.Image == constant.DefaultCloneImage {
// Default clone step
command = append(command, "CI_WORKSPACE="+e.workingdir+"/"+step.Environment["CI_REPO"])
command = append(command, "plugin-git")
} else {
// Use "image name" as run command
command = append(command, step.Image)
command = append(command, "-c")
// TODO: use commands directly
script := common.GenerateScript(step.Commands)
// Deleting the initial lines removes netrc support but adds compatibility for more shells like fish
command = append(command, "cd "+e.workingdir+"/"+step.Environment["CI_REPO"]+" && "+script[strings.Index(script, "\n\n")+2:])
}
// Prepare command
var err error
e.cmd, err = e.client.CommandContext(ctx, "/bin/env", command...)
if err != nil {
return err
}
// Get output and redirect Stderr to Stdout
std, _ := e.cmd.StdoutPipe()
e.output = readCloser{std}
e.cmd.Stderr = e.cmd.Stdout
return e.cmd.Start()
}
// WaitStep for the pipeline step to complete and returns
// the completion results.
func (e *ssh) WaitStep(context.Context, *types.Step, string) (*types.State, error) {
return &types.State{
Exited: true,
}, e.cmd.Wait()
}
// TailStep the pipeline step logs.
func (e *ssh) TailStep(context.Context, *types.Step, string) (io.ReadCloser, error) {
return e.output, nil
}
// DestroyWorkflow delete the workflow environment.
func (e *ssh) DestroyWorkflow(context.Context, *types.Config, string) error {
e.client.Close()
sftp, err := e.client.NewSftp()
if err != nil {
return err
}
return sftp.RemoveDirectory(e.workingdir)
}