-
-
Notifications
You must be signed in to change notification settings - Fork 349
/
ssh.go
152 lines (128 loc) · 3.84 KB
/
ssh.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package ssh
import (
"context"
"io"
"strings"
"github.com/melbahja/goph"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
"go.woodpecker-ci.org/woodpecker/pipeline/backend/common"
"go.woodpecker-ci.org/woodpecker/pipeline/backend/types"
"go.woodpecker-ci.org/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)
}