Skip to content
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

Merged
merged 1 commit into from
Apr 5, 2017
Merged

add URL support #64

merged 1 commit into from
Apr 5, 2017

Conversation

concaf
Copy link
Collaborator

@concaf concaf commented Mar 9, 2017

fix #55

ping @tnozicka @kadel

@concaf concaf changed the title [WIP] add URL support add URL support Mar 9, 2017
@tnozicka tnozicka self-requested a review March 10, 2017 17:43
)

var (
convertExample = ` # Converts file
opencompose convert -f opencompose.yaml`
)

const (
retryAttempts = 3
Copy link
Contributor

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?

Copy link
Contributor

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 :)

Copy link
Contributor

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.

Copy link
Contributor

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -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 {
Copy link
Contributor

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 ?

Copy link
Collaborator Author

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?

Copy link
Contributor

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.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

closed

Copy link
Contributor

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.

Copy link
Contributor

@tnozicka tnozicka Mar 14, 2017

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.

// 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")
Copy link
Contributor

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?

Copy link
Contributor

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)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Copy link
Contributor

@tnozicka tnozicka left a 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

// Check if the passed resource is a URL or not
if strings.HasPrefix(file, "http://") || strings.HasPrefix(file, "https://") {

// Validate URL
Copy link
Contributor

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

Copy link
Collaborator Author

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)
Copy link
Contributor

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.

Copy link
Contributor

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)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@@ -62,15 +69,39 @@ func GetValidatedObject(v *viper.Viper, cmd *cobra.Command, out, outerr io.Write
}

var ocObjects []*object.OpenCompose
var data []byte
Copy link
Contributor

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

Copy link
Collaborator Author

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?

Copy link
Contributor

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

Copy link
Collaborator Author

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 :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

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://") {

Copy link
Contributor

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

Copy link
Collaborator Author

@concaf concaf Mar 14, 2017

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) {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/\n//

Copy link
Collaborator Author

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
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/\n/\n\n/

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed?

Copy link
Collaborator Author

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?

Copy link
Contributor

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 tnozicka self-requested a review March 14, 2017 10:35
@tnozicka tnozicka modified the milestones: Sprint 129, v0.1.0 Mar 14, 2017
@kadel kadel modified the milestones: Sprint 129, v0.1.0 Mar 14, 2017
@concaf
Copy link
Collaborator Author

concaf commented Mar 15, 2017

@tnozicka PTAL, made the changes!

@tnozicka
Copy link
Contributor

@containscafeine tests are failing https://travis-ci.org/redhat-developer/opencompose/jobs/211376290
try make format

I'll review it after.

@concaf
Copy link
Collaborator Author

concaf commented Mar 15, 2017

@tnozicka oops, sorry about that, fixed :)

Copy link
Contributor

@tnozicka tnozicka left a 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
}
Copy link
Contributor

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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ValidateFetchReadURL->GetURLData?

Copy link
Collaborator Author

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)
Copy link
Contributor

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/

Copy link
Collaborator Author

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
Copy link
Contributor

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)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


for _, tt := range tests {
_, err := ValidateFetchReadURL(tt.url, testAttempts)
if err != nil && tt.succeed {
Copy link
Contributor

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missed this somehow, fixed

}

for _, tt := range tests {
_, err := ValidateFetchReadURL(tt.url, testAttempts)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed, TIL, thanks :)

url string
succeed bool
}{
"example.com", true,
Copy link
Contributor

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added more cases, fixed!

@concaf
Copy link
Collaborator Author

concaf commented Mar 16, 2017

@tnozicka thanks for the review :) I've made the fixes, PTAL.
Will squash once this is LGTM.

Copy link
Contributor

@tnozicka tnozicka left a 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)
Copy link
Contributor

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/

Copy link
Collaborator Author

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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I would think there are other successful status codes than just 200. In general the whole 2xx family. 203 comes to my mind and I think it would be a valid response returning data. There might be more.

Copy link
Collaborator Author

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.

Copy link
Member

@kadel kadel Mar 27, 2017

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)
Copy link
Contributor

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.")

Copy link
Contributor

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.

Copy link
Collaborator Author

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?

Copy link
Collaborator Author

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.

Copy link
Member

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.


for _, tt := range tests {
t.Run(fmt.Sprintf("passing %v, expected to succeed: %v", tt.url, tt.succeed),
func(t *testing.T) {
Copy link
Contributor

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

func TestGetURLData(t *testing.T) {
tests := []struct {
url string
succeed bool
Copy link
Contributor

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

)

const (
testAttempts = 2
Copy link
Contributor

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?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no real reason, fixed.

}
for _, tt := range tests {
t.Run(fmt.Sprintf("URL %v, expected output: %v", tt.url, tt.succeed),
func(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@tnozicka
Copy link
Contributor

Also it would be nice to mention it in README so you get more credit :)

@concaf concaf force-pushed the specify_url branch 2 times, most recently from 51639ac to bc90db4 Compare March 23, 2017 09:32
@concaf
Copy link
Collaborator Author

concaf commented Mar 23, 2017

@tnozicka added to README, and made the fixes. There are also some go fmt changes in there. PTAL.

kadel
kadel previously approved these changes Mar 27, 2017
// 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) {
Copy link
Member

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? 😇

Copy link
Collaborator Author

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?

Copy link
Member

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

@surajssd
Copy link
Contributor

@containscafeine please don't refer commits with pound sign, cause github is referring it to some old issues/PRs!

@kadel
Copy link
Member

kadel commented Mar 28, 2017

I think this is ready to be merged once you squash commits

@concaf
Copy link
Collaborator Author

concaf commented Mar 28, 2017

@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()
Copy link
Member

@kadel kadel Mar 30, 2017

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.

Copy link
Collaborator Author

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.

@kadel kadel dismissed their stale review March 30, 2017 13:50

defer in wrong place?

@concaf concaf force-pushed the specify_url branch 2 times, most recently from b416240 to 56fd227 Compare April 3, 2017 07:03
@concaf
Copy link
Collaborator Author

concaf commented Apr 3, 2017

@kadel rebased :)

kadel
kadel previously approved these changes Apr 3, 2017
Copy link
Member

@kadel kadel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@kadel kadel dismissed their stale review April 4, 2017 12:01

not working

@kadel
Copy link
Member

kadel commented Apr 4, 2017

its not working for me :-(

▶ ./opencompose convert -f https://raw.githubusercontent.com/kadel/opencompose/sprint-demo2/examples/hello-nginx-external.yaml
ERROR: unable to read file 'https://raw.githubusercontent.com/kadel/opencompose/sprint-demo2/examples/hello-nginx-external.yaml': open https://raw.githubusercontent.com/kadel/opencompose/sprint-demo2/examples/hello-nginx-external.yaml: no such file or directory

@concaf
Copy link
Collaborator Author

concaf commented Apr 4, 2017

@kadel the STDIN PR broke this :(
Fixed now, PTAL.

Try -
opencompose convert -f https://gist.github.com/containscafeine/61ec907bcacb776b3e520b529ecdd25e/raw/1051b610f80e9a319ecb9be8b621c551f69dd15f/opencompose.yaml

Copy link
Contributor

@tnozicka tnozicka left a 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

}

for _, tt := range tests {
t.Run(fmt.Sprintf("passing %v, expected to succeed: %v", tt.url, tt.succeed), func(t *testing.T) {
Copy link
Contributor

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

{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) {
Copy link
Contributor

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

Copy link
Collaborator Author

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
@concaf
Copy link
Collaborator Author

concaf commented Apr 5, 2017

@tnozicka fixed, PTAL

@tnozicka
Copy link
Contributor

tnozicka commented Apr 5, 2017

@containscafeine thanks, lgtm.

@kadel PTAL and merge if you see fit

@kadel kadel merged commit d7d01b7 into redhat-developer:master Apr 5, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow specifying file as URL
4 participants