Skip to content

Commit b61eb00

Browse files
committed
feat(api): enhance sandbox and environment management with SSH key generation and SFTP support
1 parent 2af77d0 commit b61eb00

File tree

5 files changed

+188
-18
lines changed

5 files changed

+188
-18
lines changed

environment_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ func TestEnvironmentUpdate(t *testing.T) {
123123
)
124124
_, err := client.Environments.Update(context.TODO(), gitpod.EnvironmentUpdateParams{
125125
Body: gitpod.EnvironmentUpdateParamsBodyMetadata{
126+
126127
Metadata: gitpod.F[any](map[string]interface{}{}),
127128
},
128129
})

go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@ module github.com/stainless-sdks/gitpod-go
22

33
go 1.21
44

5+
require github.com/pkg/sftp v1.13.7
6+
57
require (
68
github.com/google/uuid v1.3.0 // indirect
9+
github.com/kr/fs v0.1.0 // indirect
710
github.com/tidwall/gjson v1.14.4 // indirect
811
github.com/tidwall/match v1.1.1 // indirect
912
github.com/tidwall/pretty v1.2.1 // indirect
1013
github.com/tidwall/sjson v1.2.5 // indirect
14+
golang.org/x/crypto v0.17.0 // indirect
15+
golang.org/x/sys v0.15.0 // indirect
1116
)

go.sum

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
1+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
13
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
24
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
5+
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
6+
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
7+
github.com/pkg/sftp v1.13.7 h1:uv+I3nNJvlKZIQGSr8JVQLNHFU9YhhNpvC14Y6KgmSM=
8+
github.com/pkg/sftp v1.13.7/go.mod h1:KMKI0t3T6hfA+lTR/ssZdunHo+uwq7ghoN09/FSu3DY=
9+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
10+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
11+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
12+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
13+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
314
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
415
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
516
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
@@ -10,3 +21,46 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
1021
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
1122
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
1223
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
24+
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
25+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
26+
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
27+
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
28+
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
29+
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
30+
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
31+
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
32+
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
33+
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
34+
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
35+
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
36+
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
37+
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
38+
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
39+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
40+
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
41+
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
42+
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
43+
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
44+
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
45+
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
46+
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
47+
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
48+
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
49+
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
50+
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
51+
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
52+
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
53+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
54+
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
55+
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
56+
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
57+
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
58+
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
59+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
60+
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
61+
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
62+
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
63+
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
64+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
65+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
66+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

lib/environment.go

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ func (s *environmentService) Create(ctx context.Context, options *CreateOptions)
3838
if err != nil {
3939
return nil, err
4040
}
41-
return s.waitForRunning(ctx, envID)
41+
return waitForRunning(ctx, s.Client, envID, func(env *gitpod.EnvironmentGetResponseEnvironment) (bool, error) {
42+
return true, nil
43+
})
4244
}
4345

4446
func (s *environmentService) Start(ctx context.Context, environmentID string) (*gitpod.EnvironmentGetResponseEnvironment, error) {
@@ -48,7 +50,9 @@ func (s *environmentService) Start(ctx context.Context, environmentID string) (*
4850
if err != nil {
4951
return nil, err
5052
}
51-
return s.waitForRunning(ctx, environmentID)
53+
return waitForRunning(ctx, s.Client, environmentID, func(env *gitpod.EnvironmentGetResponseEnvironment) (bool, error) {
54+
return true, nil
55+
})
5256
}
5357

5458
func (s *environmentService) Stop(ctx context.Context, environmentID string) (*gitpod.EnvironmentGetResponseEnvironment, error) {
@@ -58,7 +62,7 @@ func (s *environmentService) Stop(ctx context.Context, environmentID string) (*g
5862
if err != nil {
5963
return nil, err
6064
}
61-
return s.waitForStopped(ctx, environmentID)
65+
return waitForStopped(ctx, s.Client, environmentID)
6266
}
6367

6468
func (s *environmentService) Delete(ctx context.Context, environmentID string) error {
@@ -72,12 +76,12 @@ func (s *environmentService) Delete(ctx context.Context, environmentID string) e
7276
}
7377
return err
7478
}
75-
return s.waitForDeleted(ctx, environmentID)
79+
return waitForDeleted(ctx, s.Client, environmentID)
7680
}
7781

78-
func (s *environmentService) waitForDeleted(ctx context.Context, envID string) error {
79-
_, err := s.waitForEnvironment(ctx, envID, func() (*gitpod.EnvironmentGetResponseEnvironment, bool, error) {
80-
resp, err := s.Client.Environments.Get(ctx, gitpod.EnvironmentGetParams{
82+
func waitForDeleted(ctx context.Context, client *gitpod.Client, envID string) error {
83+
_, err := waitForEnvironment(ctx, client, envID, func() (*gitpod.EnvironmentGetResponseEnvironment, bool, error) {
84+
resp, err := client.Environments.Get(ctx, gitpod.EnvironmentGetParams{
8185
EnvironmentID: gitpod.String(envID),
8286
})
8387
if err != nil {
@@ -92,9 +96,9 @@ func (s *environmentService) waitForDeleted(ctx context.Context, envID string) e
9296
return err
9397
}
9498

95-
func (s *environmentService) waitForStopped(ctx context.Context, envID string) (*gitpod.EnvironmentGetResponseEnvironment, error) {
96-
return s.waitForEnvironment(ctx, envID, func() (*gitpod.EnvironmentGetResponseEnvironment, bool, error) {
97-
resp, err := s.Client.Environments.Get(ctx, gitpod.EnvironmentGetParams{
99+
func waitForStopped(ctx context.Context, client *gitpod.Client, envID string) (*gitpod.EnvironmentGetResponseEnvironment, error) {
100+
return waitForEnvironment(ctx, client, envID, func() (*gitpod.EnvironmentGetResponseEnvironment, bool, error) {
101+
resp, err := client.Environments.Get(ctx, gitpod.EnvironmentGetParams{
98102
EnvironmentID: gitpod.String(envID),
99103
})
100104
if err != nil {
@@ -109,9 +113,9 @@ func (s *environmentService) waitForStopped(ctx context.Context, envID string) (
109113
})
110114
}
111115

112-
func (s *environmentService) waitForRunning(ctx context.Context, envID string) (*gitpod.EnvironmentGetResponseEnvironment, error) {
113-
return s.waitForEnvironment(ctx, envID, func() (*gitpod.EnvironmentGetResponseEnvironment, bool, error) {
114-
resp, err := s.Client.Environments.Get(ctx, gitpod.EnvironmentGetParams{
116+
func waitForRunning(ctx context.Context, client *gitpod.Client, envID string, check func(*gitpod.EnvironmentGetResponseEnvironment) (bool, error)) (*gitpod.EnvironmentGetResponseEnvironment, error) {
117+
return waitForEnvironment(ctx, client, envID, func() (*gitpod.EnvironmentGetResponseEnvironment, bool, error) {
118+
resp, err := client.Environments.Get(ctx, gitpod.EnvironmentGetParams{
115119
EnvironmentID: gitpod.String(envID),
116120
})
117121
if err != nil {
@@ -122,7 +126,11 @@ func (s *environmentService) waitForRunning(ctx context.Context, envID string) (
122126
return nil, false, fmt.Errorf("environment creation failed: %s", fm)
123127
}
124128
if resp.Environment.Status.Phase == gitpod.EnvironmentGetResponseEnvironmentStatusPhaseEnvironmentPhaseRunning {
125-
return &resp.Environment, true, nil
129+
ok, err := check(&resp.Environment)
130+
if err != nil {
131+
return nil, false, err
132+
}
133+
return &resp.Environment, ok, nil
126134
}
127135
if resp.Environment.Status.Phase == gitpod.EnvironmentGetResponseEnvironmentStatusPhaseEnvironmentPhaseStopping {
128136
return nil, false, errors.New("environment creation failed: environment is stopping")
@@ -140,11 +148,11 @@ func (s *environmentService) waitForRunning(ctx context.Context, envID string) (
140148
})
141149
}
142150

143-
func (s *environmentService) waitForEnvironment(ctx context.Context, envID string, check func() (*gitpod.EnvironmentGetResponseEnvironment, bool, error)) (*gitpod.EnvironmentGetResponseEnvironment, error) {
144-
return waitFor(ctx, s.Client, envID, gitpod.EventWatchResponseResourceTypeResourceTypeEnvironment, envID, check)
151+
func waitForEnvironment(ctx context.Context, client *gitpod.Client, envID string, check func() (*gitpod.EnvironmentGetResponseEnvironment, bool, error)) (*gitpod.EnvironmentGetResponseEnvironment, error) {
152+
return WaitFor(ctx, client, envID, gitpod.EventWatchResponseResourceTypeResourceTypeEnvironment, envID, check)
145153
}
146154

147-
func waitFor[T any](ctx context.Context, client *gitpod.Client, envID string, resourceType gitpod.EventWatchResponseResourceType, resourceID string, check func() (*T, bool, error)) (*T, error) {
155+
func WaitFor[T any](ctx context.Context, client *gitpod.Client, envID string, resourceType gitpod.EventWatchResponseResourceType, resourceID string, check func() (*T, bool, error)) (*T, error) {
148156
env, ok, err := check()
149157
if err != nil {
150158
return nil, err

lib/sandbox.go

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,19 @@ package lib
22

33
import (
44
"context"
5+
"crypto/ed25519"
56
"fmt"
67
"io"
78
"math/rand/v2"
89
"net/http"
10+
"net/url"
911

1012
"github.com/stainless-sdks/gitpod-go"
13+
14+
"github.com/pkg/sftp"
15+
"golang.org/x/crypto/ssh"
16+
17+
cryptorand "crypto/rand"
1118
)
1219

1320
type Sandbox interface {
@@ -36,7 +43,21 @@ func NewSandbox(ctx context.Context, client *gitpod.Client, environmentID string
3643
return nil, nil
3744
}
3845

46+
/*signer, err := generateSSHKey()
47+
if err != nil {
48+
return nil, err
49+
}
50+
_, err = client.Environments.Update(ctx, gitpod.EnvironmentUpdateParams{
51+
Body: gitpod.EnvironmentUpdateParamsBodySpec{
52+
Spec: ,
53+
},
54+
})
55+
if err != nil {
56+
return nil, err
57+
}*/
58+
3959
return &sandbox{
60+
taskReference: taskReference,
4061
taskID: resp.Task.ID,
4162
environmentID: environmentID,
4263
logAccessToken: logTokenResp.AccessToken,
@@ -45,10 +66,12 @@ func NewSandbox(ctx context.Context, client *gitpod.Client, environmentID string
4566
}
4667

4768
type sandbox struct {
69+
taskReference string
4870
taskID string
4971
environmentID string
5072
logAccessToken string
5173
client *gitpod.Client
74+
sshSigner ssh.Signer
5275
}
5376

5477
func (s *sandbox) Close() error {
@@ -75,7 +98,7 @@ func (s *sandbox) Exec(ctx context.Context, command string) (io.ReadCloser, erro
7598
return nil, err
7699
}
77100
taskExecutionID := startResp.TaskExecution.ID
78-
taskExecution, err := waitFor(ctx, s.client, s.environmentID, gitpod.EventWatchResponseResourceTypeResourceTypeTaskExecution, taskExecutionID, func() (*gitpod.EnvironmentAutomationTaskExecutionGetResponseTaskExecution, bool, error) {
101+
taskExecution, err := WaitFor(ctx, s.client, s.environmentID, gitpod.EventWatchResponseResourceTypeResourceTypeTaskExecution, taskExecutionID, func() (*gitpod.EnvironmentAutomationTaskExecutionGetResponseTaskExecution, bool, error) {
79102
resp, err := s.client.Environments.Automations.Tasks.Executions.Get(ctx, gitpod.EnvironmentAutomationTaskExecutionGetParams{
80103
ID: gitpod.String(taskExecutionID),
81104
})
@@ -103,3 +126,82 @@ func (s *sandbox) Exec(ctx context.Context, command string) (io.ReadCloser, erro
103126
}
104127
return logResp.Body, nil
105128
}
129+
130+
type FS struct {
131+
*sftp.Client
132+
sshConn *ssh.Client
133+
}
134+
135+
func (fs *FS) Close() error {
136+
return fs.Client.Close()
137+
}
138+
139+
func (s *sandbox) FS(ctx context.Context) (*FS, error) {
140+
env, err := waitForRunning(ctx, s.client, s.environmentID, func(env *gitpod.EnvironmentGetResponseEnvironment) (bool, error) {
141+
keySpecified := false
142+
for _, key := range env.Spec.SSHPublicKeys {
143+
if key.ID == s.taskReference {
144+
keySpecified = true
145+
break
146+
}
147+
}
148+
if !keySpecified {
149+
return false, fmt.Errorf("public key not specified")
150+
}
151+
keyApplied := false
152+
for _, key := range env.Status.SSHPublicKeys {
153+
if key.ID == s.taskReference {
154+
if key.Phase == gitpod.EnvironmentGetResponseEnvironmentStatusSSHPublicKeysPhaseContentPhaseFailed {
155+
return false, fmt.Errorf("public key failed to apply")
156+
}
157+
if key.Phase == gitpod.EnvironmentGetResponseEnvironmentStatusSSHPublicKeysPhaseContentPhaseReady {
158+
keyApplied = true
159+
break
160+
}
161+
}
162+
}
163+
return keyApplied && env.Status.EnvironmentURLs.SSH.URL == "", nil
164+
})
165+
if err != nil {
166+
return nil, err
167+
}
168+
sshURL := env.Status.EnvironmentURLs.SSH.URL
169+
parsedURL, err := url.Parse(sshURL)
170+
if err != nil {
171+
return nil, err
172+
}
173+
174+
cfg := ssh.ClientConfig{
175+
User: "gitpod_devcontainer",
176+
Auth: []ssh.AuthMethod{
177+
ssh.PublicKeys(s.sshSigner),
178+
},
179+
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
180+
}
181+
clnt, err := ssh.Dial("tcp", parsedURL.Host, &cfg)
182+
if err != nil {
183+
return nil, err
184+
}
185+
fs, err := sftp.NewClient(clnt)
186+
if err != nil {
187+
return nil, err
188+
}
189+
return &FS{
190+
Client: fs,
191+
sshConn: clnt,
192+
}, nil
193+
}
194+
195+
func generateSSHKey() (privateKeyPEM ssh.Signer, err error) {
196+
_, privateKey, err := ed25519.GenerateKey(cryptorand.Reader)
197+
if err != nil {
198+
return nil, fmt.Errorf("failed to generate key pair: %v", err)
199+
}
200+
201+
signer, err := ssh.NewSignerFromKey(privateKey)
202+
if err != nil {
203+
return nil, fmt.Errorf("failed to create signer from private key: %v", err)
204+
}
205+
206+
return signer, nil
207+
}

0 commit comments

Comments
 (0)