Add SSH SFTP backend support

This commit is contained in:
2026-05-31 16:53:37 +00:00
parent 1ad566264f
commit 84f77ec0d0
29 changed files with 1629 additions and 40 deletions

View File

@@ -53,7 +53,9 @@ pipelines:
html: false
- id: static-site
backend: ssh
uri: ssh://deploy@example.com:22
host: example.com
user: deploy
port: 22
path: /srv/www/reports
publish:
source: false
@@ -87,13 +89,19 @@ pipelines:
- id: ssh-backend
source:
backend: ssh
uri: ssh://reports@example.com:22
host: source.example.com
user: reports
path: /source
destinations:
- id: ssh-destination
backend: ssh
uri: ssh://deploy@example.com:22
host: destination.example.com
user: deploy
port: 2222
path: /destination
ssh_key_file: /home/deploy/.ssh/id_ed25519
known_hosts: /home/deploy/.ssh/known_hosts
host_key_policy: strict
`,
"s3": `
pipelines:
@@ -171,7 +179,7 @@ func TestLoadFileRejectsMissingRequiredFields(t *testing.T) {
"destinations": `pipelines: [{id: reports, source: {backend: local, path: /source}}]`,
"destination id": `pipelines: [{id: reports, source: {backend: local, path: /source}, destinations: [{backend: local, path: /archive}]}]`,
"local path": `pipelines: [{id: reports, source: {backend: local}, destinations: [{id: archive, backend: local, path: /archive}]}]`,
"ssh uri": `pipelines: [{id: reports, source: {backend: ssh, path: /source}, destinations: [{id: archive, backend: local, path: /archive}]}]`,
"ssh host": `pipelines: [{id: reports, source: {backend: ssh, path: /source}, destinations: [{id: archive, backend: local, path: /archive}]}]`,
"s3 bucket": `pipelines: [{id: reports, source: {backend: s3, endpoint: "https://s3.example.com"}, destinations: [{id: archive, backend: local, path: /archive}]}]`,
"publish outputs": `pipelines: [{id: reports, source: {backend: local, path: /source}, destinations: [{id: archive, backend: local, path: /archive, publish: {source: false, html: false}}]}]`,
}
@@ -183,6 +191,88 @@ func TestLoadFileRejectsMissingRequiredFields(t *testing.T) {
}
}
func TestLoadFileDefaultsSSHConfig(t *testing.T) {
cfg := loadConfig(t, `
pipelines:
- id: ssh-defaults
source:
backend: ssh
host: source.example.com
path: /source
destinations:
- id: archive
backend: ssh
host: destination.example.com
path: /archive
host_key_policy: false
`)
source := cfg.Pipelines[0].Source
if source.Port != 22 {
t.Fatalf("source port = %d, want 22", source.Port)
}
if source.SSH.HostKeyPolicy != HostKeyPolicyAcceptNew {
t.Fatalf("source host key policy = %q, want accept-new", source.SSH.HostKeyPolicy)
}
destination := cfg.Pipelines[0].Destinations[0]
if destination.Port != 22 {
t.Fatalf("destination port = %d, want 22", destination.Port)
}
if destination.SSH.HostKeyPolicy != HostKeyPolicyOff {
t.Fatalf("destination host key policy = %q, want off", destination.SSH.HostKeyPolicy)
}
}
func TestLoadFileNormalizesSSHHostKeyPolicies(t *testing.T) {
tests := map[string]HostKeyPolicy{
`true`: HostKeyPolicyStrict,
`"true"`: HostKeyPolicyStrict,
`strict`: HostKeyPolicyStrict,
`accept-new`: HostKeyPolicyAcceptNew,
`false`: HostKeyPolicyOff,
`"false"`: HostKeyPolicyOff,
`off`: HostKeyPolicyOff,
`"STRICT"`: HostKeyPolicyStrict,
`"ACCEPT-NEW"`: HostKeyPolicyAcceptNew,
`"OFF"`: HostKeyPolicyOff,
}
for value, want := range tests {
t.Run(value, func(t *testing.T) {
cfg := loadConfig(t, `
pipelines:
- id: ssh-policy
source:
backend: ssh
host: source.example.com
path: /source
host_key_policy: `+value+`
destinations:
- id: archive
backend: local
path: /archive
`)
if got := cfg.Pipelines[0].Source.SSH.HostKeyPolicy; got != want {
t.Fatalf("host key policy = %q, want %q", got, want)
}
})
}
}
func TestLoadFileRejectsSSHURIExecutionConfig(t *testing.T) {
assertLoadError(t, `
pipelines:
- id: reports
source:
backend: ssh
uri: ssh://reports@example.com:22
path: /source
destinations:
- id: archive
backend: local
path: /archive
`, "uri is not supported for ssh backend")
}
func TestLoadFileRejectsUnsupportedBackend(t *testing.T) {
assertLoadError(t, `
pipelines:
@@ -267,6 +357,7 @@ func TestExampleConfigsLoad(t *testing.T) {
"../../examples/local-publish.yml",
"../../examples/local-html.yml",
"../../examples/fan-out.yml",
"../../examples/ssh-destination.yml",
} {
t.Run(path, func(t *testing.T) {
if _, err := LoadFile(path); err != nil {