-
Notifications
You must be signed in to change notification settings - Fork 12
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
add URL support #64
add URL support #64
Conversation
) | ||
|
||
var ( | ||
convertExample = ` # Converts file | ||
opencompose convert -f opencompose.yaml` | ||
) | ||
|
||
const ( | ||
retryAttempts = 3 |
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.
shouldn't this be all caps?
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.
not really, C (and MACROS) ages are gone :)
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 know it's not a C age, it's just that it becomes easier to understand just by looking at code that it's a constant and no need to find it's declaration in immediate scope.
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.
@surajssd well, the block is called const
that should help you. But seriously in Golang case matters - if you change it to all uppercase you export that constant outside of the package
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.
@surajssd also, some context from k8s - https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/resource/visitor.go#L44-L47
pkg/cmd/convert.go
Outdated
@@ -62,20 +69,44 @@ func GetValidatedObject(v *viper.Viper, cmd *cobra.Command, out, outerr io.Write | |||
} | |||
|
|||
var ocObjects []*object.OpenCompose | |||
var data []byte | |||
var err error | |||
for _, file := range files { |
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 this be better named as filepath
s/file/filepath
?
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.
@surajssd I didn't edit this part of code in this PR, let's send this a separate one?
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 don't think it needs renaming. Especially as it's not changed here. (Btw. Go guidelines actually tell you to use really short names, but to be fair it's no applicable everywhere.)
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.
closed
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.
Using filePath makes more sense over using file because it conveys the meaning of what that variable has, here it has path to the file than file itself.
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.
@surajssd I would say it is really the last think we should be discussing at this point and it is not a subject of this PR.
pkg/cmd/convert.go
Outdated
// Read from the response body, ioutil.ReadAll will return []byte | ||
data, err = ioutil.ReadAll(body) | ||
if err != nil { | ||
return nil, fmt.Errorf("Reading from response body failed") |
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.
how about using the wrapping library right from now, for errors?
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 the Golang way is
return nil, fmt.Errorf("reading from response body failed: %s", err)
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.
fixed
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.
- some nits
- exctraction into a function
- needs unit tests
pkg/cmd/convert.go
Outdated
// Check if the passed resource is a URL or not | ||
if strings.HasPrefix(file, "http://") || strings.HasPrefix(file, "https://") { | ||
|
||
// Validate URL |
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.
could you move the logic bellow into a separate function? it will make better separation of concerns and it will be separately testable without creating cobra objects
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.
fixed
pkg/util/util.go
Outdated
} | ||
|
||
// retry if http.Get fails | ||
response, err = http.Get(url) |
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.
Having :=
instead of pre-declaring response
would look more like Golang.
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.
(in general I declare a variable upfront only if it extends the scope or to avoid shadowing)
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.
fixed
pkg/cmd/convert.go
Outdated
@@ -62,15 +69,39 @@ func GetValidatedObject(v *viper.Viper, cmd *cobra.Command, out, outerr io.Write | |||
} | |||
|
|||
var ocObjects []*object.OpenCompose | |||
var data []byte |
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 that these two variable should stay declared inside the loop as they were before. Otherwise you are extending the scope and a life time of them which can be error prone
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.
@tnozicka we need to use data
outside the if else statement, for passing it to decoder, etc. How do you suggest I do this without declaring this before if else?
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.
@containscafeine you don't use data
outside of that for. declare it before the first if
inside for
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.
Oh wait, I put it outside the loop, it should be inside the loop out the if, else; sorry got a bit confused :)
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.
fixed
pkg/cmd/convert.go
Outdated
return nil, fmt.Errorf("unable to read file '%s': %s", file, err) | ||
// Check if the passed resource is a URL or not | ||
if strings.HasPrefix(file, "http://") || strings.HasPrefix(file, "https://") { | ||
|
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.
we can leave out this empty line after if
so it looks like the rest of the code
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.
fixed
pkg/util/util.go
Outdated
// Wait for <duration> time between each try. | ||
// This return the response body upon successful fetch | ||
func FetchURLWithRetries(url string, attempts int, duration time.Duration) (io.ReadCloser, 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.
s/\n//
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.
fixed
pkg/util/util.go
Outdated
} | ||
|
||
body = response.Body | ||
} |
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.
s/\n/\n\n/
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.
fixed?
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.
@tnozicka not sure about what do you want me to change here?
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.
@containscafeine the was an empty line missing, now it's ok
@tnozicka PTAL, made the changes! |
@containscafeine tests are failing https://travis-ci.org/redhat-developer/opencompose/jobs/211376290 I'll review it after. |
@tnozicka oops, sorry about that, fixed :) |
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.
@containscafeine I am bit in a rush here because I wanted to make it before going on PTO; I hope it will make sense otherwise it's most probably my fault ;)
pkg/util/util.go
Outdated
} | ||
|
||
body = response.Body | ||
} |
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.
@containscafeine the was an empty line missing, now it's ok
pkg/util/util.go
Outdated
"time" | ||
) | ||
|
||
func ValidateFetchReadURL(urlString string, attempts int) ([]byte, 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.
ValidateFetchReadURL->GetURLData?
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.
makes sense, fixed
pkg/util/util.go
Outdated
// Validate URL | ||
_, err := url.ParseRequestURI(urlString) | ||
if err != nil { | ||
return nil, fmt.Errorf("%v: invalid URL", urlString) |
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.
s/"%v: invalid URL/"invalid URL: %v/
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.
fixed
pkg/util/util.go
Outdated
// Fetch the URL and store the response body | ||
body, err := FetchURLWithRetries(urlString, attempts, time.Second) | ||
if err != nil { | ||
return nil, err |
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 you should wrap that error with an error message like:
fmt.Errorf("FetchURLWithRetries failed: %s", err)
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.
fixed
pkg/util/util_test.go
Outdated
|
||
for _, tt := range tests { | ||
_, err := ValidateFetchReadURL(tt.url, testAttempts) | ||
if err != nil && tt.succeed { |
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 won't work for a case where the test should have failed but it succeeded
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.
missed this somehow, fixed
pkg/util/util_test.go
Outdated
} | ||
|
||
for _, tt := range tests { | ||
_, err := ValidateFetchReadURL(tt.url, testAttempts) |
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.
We are moving to using Golang subtests. Please use t.Run as in https://github.com/tnozicka/opencompose/blob/a21430158830bff240a8252aa4b609e994907238/pkg/encoding/v1/encoding_test.go#L207
You can find out more on https://blog.golang.org/subtests
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.
fixed, TIL, thanks :)
pkg/util/util_test.go
Outdated
url string | ||
succeed bool | ||
}{ | ||
"example.com", true, |
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'd like to see here more cases or you can maybe cover it well even from the TestValidateFetchReadURL. But I am generally missing a fail case here and an empty string case.
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.
added more cases, fixed!
@tnozicka thanks for the review :) I've made the fixes, PTAL. |
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.
@containscafeine thanks for addressing the previous review and adding test. The structure and tests are looking good; there is a small issue with retry logic though
pkg/util/util.go
Outdated
} | ||
|
||
// Fetch the URL and store the response body | ||
body, err := FetchURLWithRetries(urlString, attempts, time.Second) |
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 would prefer to be more explicit here: s/time.Second/1*time.Second/
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.
fixed
pkg/util/util.go
Outdated
} | ||
|
||
// if the status code is not 200 OK, return an error | ||
if response.StatusCode != http.StatusOK { |
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 would think there are other successful status codes than just
200
. In general the whole2xx
family.203
comes to my mind and I think it would be a valid response returning data. There might be more.
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.
@tnozicka while going through the entire 200 status list, I'm not sure if all them can be treated as OK.
Also, Kubernetes for reference - https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/resource/visitor.go#L269
I partially agree with you, but I don't think we should do this for the entire 200 family. Let me know what should we go ahead with this, I'm a bit divided on this.
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.
In out case only 200 is success, maybe also 203, but not sure if it makes sense in this use-case.
I wound't worry about it.
pkg/util/util.go
Outdated
|
||
// if the status code is not 200 OK, return an error | ||
if response.StatusCode != http.StatusOK { | ||
return nil, fmt.Errorf("unable to fetch %v, server returned status code %v", url, response.StatusCode) |
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.
If 3 request would hit e.g. 404
, 404
, 200
this condition would end up with the first one and didn't continue, right? This may be because of cache propagation. I would consider retrying in that case. (citing the 404
description: "The requested resource could not be found but may be available in the future. Subsequent requests by the client are permissible.")
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 here is a resource leak. You are not closing the body
. I might be best if you would use defer + readAll in this function and returned just the data.
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.
If 3 request would hit e.g. 404, 404, 200 this condition would end up with the first one and didn't continue, right? This may be because of cache propagation. I would consider retrying in that case. (citing the 404 description: "The requested resource could not be found but may be available in the future. Subsequent requests by the client are permissible.")
Yep, if a 404 is encountered in the first time, no retries are made and opencompose errors out.
The cache propagation case you have mentioned seems like an edge case, should we really take that into consideration in opencompose?
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 here is a resource leak. You are not closing the body. I might be best if you would use defer + readAll in this function and returned just the data.
I'm closing the body in GetURLData, but I've modified FetchURLWithRetries to return the data as []byte now.
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.
RE: error 404
I don't think we should retry on 404.
Even if official description says "may be available in the future" I don't think it makes sense to retry it in couple of seconds. It should display error.
pkg/util/util_test.go
Outdated
|
||
for _, tt := range tests { | ||
t.Run(fmt.Sprintf("passing %v, expected to succeed: %v", tt.url, tt.succeed), | ||
func(t *testing.T) { |
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 would prefer to merge this line with the one above. It will save you one \t
for all the commands inside it.
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.
fixed
pkg/util/util_test.go
Outdated
func TestGetURLData(t *testing.T) { | ||
tests := []struct { | ||
url string | ||
succeed bool |
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 convention in other test is to put succeed
in the first place in the struct and test - for consistency it would be nice to follow it
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.
fixed
pkg/util/util_test.go
Outdated
) | ||
|
||
const ( | ||
testAttempts = 2 |
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.
any reason not to test with value of retryAttempts
since that the hardcoded value for "prod". are the tests unnecessary slower?
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.
no real reason, fixed.
pkg/util/util_test.go
Outdated
} | ||
for _, tt := range tests { | ||
t.Run(fmt.Sprintf("URL %v, expected output: %v", tt.url, tt.succeed), | ||
func(t *testing.T) { |
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 as above
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.
fixed
Also it would be nice to mention it in README so you get more credit :) |
51639ac
to
bc90db4
Compare
@tnozicka added to README, and made the fixes. There are also some |
// This returns the data from the response body []byte upon successful fetch | ||
// The passed URL is not validated, so validate the URL before passing to this | ||
// function | ||
func FetchURLWithRetries(url string, attempts int, duration time.Duration) ([]byte, 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.
Maybe just one more stupid question here.
Do we even need retry for this? 😇
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.
@kadel imo, it's a nice to have, and since the code is already there in this PR, we can keep it.
Anyway, it retries only if getting a valid resource fails, so should be fine, WDYT?
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.
yeh, I was just wondering if there was some reason for it. 👍
its ok
@containscafeine please don't refer commits with pound sign, cause github is referring it to some old issues/PRs! |
I think this is ready to be merged once you squash commits |
@kadel cool, squashed :) |
pkg/util/util.go
Outdated
if err != nil { | ||
return nil, fmt.Errorf("reading from response body failed: %s", err) | ||
} | ||
defer response.Body.Close() |
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.
Sorry, didn't noticed that before :-(
Shouldn't be this defer
before calling ReadAll
(just after http.Get
)?
If ReadAll
fails, defer function wont be called.
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.
@kadel, true, nice catch :)
Fixed.
b416240
to
56fd227
Compare
@kadel rebased :) |
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.
lgtm
its not working for me :-(
|
@kadel the STDIN PR broke this :( Try - |
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.
small nit, lgtm otherwise
pkg/util/util_test.go
Outdated
} | ||
|
||
for _, tt := range tests { | ||
t.Run(fmt.Sprintf("passing %v, expected to succeed: %v", tt.url, tt.succeed), func(t *testing.T) { |
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 just a test name, not a log message.
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.
fixed
pkg/util/util_test.go
Outdated
{false, "https://google.com/giveme404"}, // test for !200 status code | ||
} | ||
for _, tt := range tests { | ||
t.Run(fmt.Sprintf("URL %v, expected output: %v", tt.url, tt.succeed), func(t *testing.T) { |
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.
test name, not a log message
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.
fixed
This commit allows to pass URLs besides local files to the -f/--file switch. This is done by checking if the passed in resource string starts with an http:// or https://, and if it does, the URL validation is done followed by fetching the URL with 3 retry attempts with a gap of 1 second in between. Fix redhat-developer#55
@tnozicka fixed, PTAL |
@containscafeine thanks, lgtm. @kadel PTAL and merge if you see fit |
fix #55
ping @tnozicka @kadel