-
Notifications
You must be signed in to change notification settings - Fork 1
client: add basic auth support #1
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
base: main
Are you sure you want to change the base?
Changes from all commits
25ce529
6b9138f
a65477b
725a17c
131fbe0
e6cfe16
b0096da
0a91d9f
041a9fb
e10d5fc
03555bc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -2,6 +2,9 @@ | |||||
| package cmd | ||||||
|
|
||||||
| import ( | ||||||
| "fmt" | ||||||
| "io/ioutil" | ||||||
|
|
||||||
| "github.com/spf13/cobra" | ||||||
| ) | ||||||
|
|
||||||
|
|
@@ -20,3 +23,32 @@ func Command() *cobra.Command { | |||||
|
|
||||||
| return cli | ||||||
| } | ||||||
|
|
||||||
| // machineID is a helper to return unique ID | ||||||
| // of the machine. | ||||||
| func machineID() (string, error) { | ||||||
| id, err := ioutil.ReadFile("/etc/machine-id") | ||||||
| if err != nil { | ||||||
| return "", fmt.Errorf("reading machine ID from file: %w", err) | ||||||
| } | ||||||
|
|
||||||
| return string(id), nil | ||||||
| } | ||||||
|
|
||||||
| // checkID asserts that the ID is not nil, if it's the case | ||||||
| // it uses `machineID` to generate a default one. | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We do not generate anything.
Suggested change
|
||||||
| func checkID(id *string) error { | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this function should have Maybe we can make it return |
||||||
| // the ID is set and it's not empty. | ||||||
| if id != nil && *id != "" { | ||||||
| return nil | ||||||
| } | ||||||
|
|
||||||
| i, err := machineID() | ||||||
| if err != nil { | ||||||
| return fmt.Errorf("getting default machine ID: %w", err) | ||||||
| } | ||||||
|
|
||||||
| *id = i | ||||||
|
|
||||||
| return nil | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,50 @@ | ||||||
| package client | ||||||
|
|
||||||
| import ( | ||||||
| "context" | ||||||
| "fmt" | ||||||
| "net/http" | ||||||
| ) | ||||||
|
|
||||||
| type basicAuthRoundTripper struct { | ||||||
| username string | ||||||
| password string | ||||||
| rt http.RoundTripper | ||||||
| } | ||||||
|
|
||||||
| // RoundTrip is required to implement RoundTripper interface. | ||||||
| // We check if an authorization token is already set, if not we set it. | ||||||
| // We return the initial RoundTripper to chain it in the whole process. | ||||||
| func (b *basicAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { | ||||||
| if len(req.Header.Get("Authorization")) != 0 { | ||||||
| resp, err := b.rt.RoundTrip(req) | ||||||
| if err != nil { | ||||||
| return nil, fmt.Errorf("inner round trip error: %w", err) | ||||||
| } | ||||||
|
|
||||||
| return resp, nil | ||||||
| } | ||||||
|
|
||||||
| req = req.Clone(context.TODO()) | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Otherwise using round tripper swallows the context control on the request: package client_test
import (
"context"
"errors"
"net/http"
"testing"
"time"
"github.com/flatcar-linux/fleetlock/pkg/client"
)
func Test_Cancelling_context_for_request_performed_with_http_client_with_basic_auth_round_tripper_cancels_the_request(t *testing.T) {
httpClient := http.Client{
Transport: client.NewBasicAuthRoundTripper("foo", "bar", nil),
}
requestTimeout := time.Second
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
t.Cleanup(cancel)
req, err := http.NewRequestWithContext(ctx, "GET", "http://10.255.255.1", nil)
if err != nil {
t.Fatal(err)
}
errCh := make(chan error, 1)
go func() {
_, err = httpClient.Do(req)
errCh <- err
}()
testDeadline := time.NewTimer(2 * requestTimeout)
select {
case <-testDeadline.C:
t.Fatalf("Expected request to return before the deadline")
case err := <-errCh:
if err != nil && !errors.Is(err, context.DeadlineExceeded) {
t.Fatal(err)
}
}
} |
||||||
| req.SetBasicAuth(b.username, b.password) | ||||||
|
|
||||||
| resp, err := b.rt.RoundTrip(req) | ||||||
| if err != nil { | ||||||
| return nil, fmt.Errorf("inner round trip error: %w", err) | ||||||
| } | ||||||
|
|
||||||
| return resp, nil | ||||||
| } | ||||||
|
|
||||||
| // NewBasicAuthRoundTripper returns a basicAuthRoundTripper with username and password. | ||||||
| func NewBasicAuthRoundTripper(username, password string, rt http.RoundTripper) http.RoundTripper { | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we can let
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like round tripper should have it's own set of tests, separate from the client, to make the effort required for testing it smaller. |
||||||
| if rt == nil { | ||||||
| rt = &http.Transport{} | ||||||
| } | ||||||
|
|
||||||
| return &basicAuthRoundTripper{ | ||||||
| username: username, | ||||||
| password: password, | ||||||
| rt: rt, | ||||||
| } | ||||||
| } | ||||||
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.
Making this path configurable would make the more testable, but I guess it would have to be done via e.g.
--id-from-fileflag or something. Also, we don't have any tests in place for this, so would be more effort.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 get your point, however we should not forget that
machineID()is a helper to provide a default value for the--idflag. In case one wants to use a different ID he can just use--id my-idor even--id $(cat /tmp/my-id-file).If we really want to test this helper, we can still rely on fs abstraction with
afero.FSbut it becomes a bit overkill IMHO. :)