-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
http file transfer #2055
http file transfer #2055
Conversation
e0c5544
to
3b504e3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think your overall design looks solid, I like it.
There are several rough edges that needs to be polished on the backend:
- It's not clear to me why do you need to create a new client instead of using the method of the session
- I would like you to run a check for file leaks - make sure all the resources are closed
- Apply/address my general comments
lib/client/api.go
Outdated
@@ -905,6 +906,59 @@ func (tc *TeleportClient) Play(ctx context.Context, namespace, sessionId string) | |||
return trace.Wrap(err) | |||
} | |||
|
|||
// RunSCPCommand executes scp command |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's call it ExecuteSCP
and elaborate in comments that it executes scp.Command and is used in lower-level API integrations opposed to SCP function that is used to mimic SCP CLI command behavior
lib/client/api.go
Outdated
} | ||
defer proxyClient.Close() | ||
|
||
siteInfo, err := proxyClient.currentCluster() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clusterInfo
, we are slowly renaming site
to cluster
everywhere
lib/client/api.go
Outdated
} | ||
|
||
// gets called to convert SSH error code to tc.ExitStatus | ||
onError := func(err error) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
onError is used only once, so I don't see a reason to factor this into closure, just inline the code
lib/client/api.go
Outdated
@@ -983,7 +1052,21 @@ func (tc *TeleportClient) SCP(ctx context.Context, args []string, port int, recu | |||
} | |||
// copy everything except the last arg (that's destination) | |||
for _, dest := range args[1:] { | |||
err = client.Download(src, dest, recursive, tc.Stderr, progressWriter) | |||
scpParams := scp.Config{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be scpConfig
for consistency (in similar code above, it is called scpCfg
)
lib/client/api.go
Outdated
// copy everything except the last arg (that's destination) | ||
for _, src := range args[:len(args)-1] { | ||
err = client.Upload(src, dest, recursive, tc.Stderr, progressWriter) | ||
scpCfg := scp.Config{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's call it scpConfig
for consistency
lib/web/apiserver.go
Outdated
@@ -181,6 +181,10 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*RewritingHandler, error) { | |||
h.GET("/webapi/sites/:site/namespaces/:namespace/sessions/:sid/events", h.WithClusterAuth(h.siteSessionEventsGet)) // get recorded session's timing information (from events) | |||
h.GET("/webapi/sites/:site/namespaces/:namespace/sessions/:sid/stream", h.siteSessionStreamGet) // get recorded session's bytes (from events) | |||
|
|||
// scp file transfer | |||
h.GET("/webapi/sites/:site/namespaces/:namespace/nodes/:node/:login/scp/*filepath", h.WithClusterAuth(h.downloadFile)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it feels like login
should be really a query parameter, not part of the path, will be more REST
-y this way
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also I would stick to sid
notation as in the function above for consistency instead of node
lib/web/apiserver.go
Outdated
@@ -181,6 +181,10 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*RewritingHandler, error) { | |||
h.GET("/webapi/sites/:site/namespaces/:namespace/sessions/:sid/events", h.WithClusterAuth(h.siteSessionEventsGet)) // get recorded session's timing information (from events) | |||
h.GET("/webapi/sites/:site/namespaces/:namespace/sessions/:sid/stream", h.siteSessionStreamGet) // get recorded session's bytes (from events) | |||
|
|||
// scp file transfer | |||
h.GET("/webapi/sites/:site/namespaces/:namespace/nodes/:node/:login/scp/*filepath", h.WithClusterAuth(h.downloadFile)) | |||
h.POST("/webapi/sites/:site/namespaces/:namespace/nodes/:node/:login/scp/*filepath", h.WithClusterAuth(h.uploadFile)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here
lib/web/files.go
Outdated
|
||
func (h *Handler) downloadFile(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *SessionContext, site reversetunnel.RemoteSite) (interface{}, error) { | ||
req := fileTransferRequest{ | ||
Login: p.ByName("login"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the structure is not exported, so it does make sense to use lower case for struct member fields as well
lib/web/files.go
Outdated
Username: f.ctx.user, | ||
ProxyHostPort: f.proxyHostPort, | ||
// TODO: replace os io streams with custom streams | ||
Stdout: os.Stdout, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be done before the release for sure
return nil | ||
} | ||
|
||
func (f *fileTransfer) createClient(req fileTransferRequest) (*client.TeleportClient, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wait, why not use the client from the context? there is a function there that does pretty much the same
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This behavior is similar to our web handlers for handling ssh connections. This method returns TeleportClient which then we use to connect to a node to run SCP command.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
k, can you then try to factor it into the common utility function to reduce boilerplate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i looked at 2 places where we create this config and moved initialization of common parameters to a separate method makeTeleportClientConfig
2ba7a39
to
fd43171
Compare
lib/sshutils/scp/scp.go
Outdated
// to teleport can pretend it launches real scp behind the scenes | ||
type command struct { | ||
Config | ||
*log.Entry |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you call this variable log
? Embedded *log.Entry
like this pollutes autocomplete for me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
A general comment, you have a lot of
What do you think about consolidating them? Maybe something like the following: cmd, err := scp.New(&scp.Config{
Type: scp.HTTP | scp.File,
Transfer: scp.Upload | scp.Download,
...
})
if err != nil {
return trace.Wrap(err)
} That way all the logic for how to construct the SCP command can be pushed down to the |
Since there are many flags and parameters, I figured it would be simpler for a caller if some of it can be encapsulated by specific commands so there is no need to become familiar with every aspect of SCP to create, for example, a HTTPDownload command. CreateHTTPUpload accepts only HTTP specific parameters and takes care of the rest. All this factory methods are inside scp package. |
@alex-kovoy please backport this to branch/2.7 as well |
This work is still in progress but I would like you guys to take a look to provide an early feedback.