Skip to content

Conversation

@olucasfreitas
Copy link
Contributor

@olucasfreitas olucasfreitas commented Oct 21, 2025

Details

This PR fixes a usability issue where rosa create cluster --http-proxy was displaying unhelpful error messages when proxy passwords contained special characters like forward slashes, making it difficult for users to understand what was wrong with their input.


Fixed Behavior

  1. Run the command with a proxy password containing a forward slash:

    ./rosa create cluster --http-proxy "http://proxyuser:QvoZjyy/[email protected]:8080"
  2. Now shows clear error message:

    E: Invalid http-proxy value 'http://proxyuser:QvoZjyy/[email protected]:8080': 
       password contains special characters that need URL encoding
    

Additional Validations Added

  1. Spaces in URL - Detects and rejects URLs with spaces:

    ./rosa create cluster --http-proxy "http://proxy.example.com :8080"

    Output:

    E: Invalid http-proxy value 'http://proxy.example.com :8080': URL cannot contain spaces
    
  2. Invalid Port Range - Validates port numbers are between 1 and 65535:

    ./rosa create cluster --http-proxy "http://proxy.example.com:99999"

    Output:

    E: Invalid http-proxy value 'http://proxy.example.com:99999': port must be between 1 and 65535
    
  3. Multiple @ Symbols - Detects unencoded @ in passwords:

    ./rosa create cluster --http-proxy "http://user:pass@[email protected]:8080"

    Output:

    E: Invalid http-proxy value 'http://user:pass@[email protected]:8080': 
       password contains special characters that need URL encoding
    

The --https-proxy Flag Behavior

Using HTTPS scheme with --http-proxy continues to show the existing error:

./rosa create cluster --http-proxy "https://proxy.example.com:8080"

Output:

E: Expected http-proxy to have an http:// scheme

Ticket

Closes OCM-18900

@codecov
Copy link

codecov bot commented Oct 21, 2025

Codecov Report

❌ Patch coverage is 82.00000% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 28.07%. Comparing base (5f21d22) to head (a50904c).
⚠️ Report is 5 commits behind head on master.

Files with missing lines Patch % Lines
pkg/ocm/helpers.go 75.00% 5 Missing and 2 partials ⚠️
pkg/interactive/validation.go 90.90% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3052      +/-   ##
==========================================
+ Coverage   27.91%   28.07%   +0.15%     
==========================================
  Files         316      316              
  Lines       35008    35043      +35     
==========================================
+ Hits         9774     9837      +63     
+ Misses      24582    24551      -31     
- Partials      652      655       +3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@openshift-merge-robot openshift-merge-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Oct 23, 2025
@olucasfreitas olucasfreitas force-pushed the OCM-18900 branch 2 times, most recently from 00638f4 to 120a803 Compare November 3, 2025 11:36
@openshift-merge-robot openshift-merge-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Nov 3, 2025
@olucasfreitas
Copy link
Contributor Author

/retest-required

@olucasfreitas olucasfreitas force-pushed the OCM-18900 branch 2 times, most recently from 2260456 to c6b94b4 Compare November 3, 2025 14:46
@olucasfreitas
Copy link
Contributor Author

/retest-required

@olucasfreitas
Copy link
Contributor Author

/retest-required

Copy link
Contributor

@hunterkepley hunterkepley left a comment

Choose a reason for hiding this comment

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

The test case in the bug ticket does not seem to pass:

E: parse "http://proxyuser:QvoZjyy/[email protected]:8080": invalid port ":QvoZjyy" after host

I think there may be a bug in the parse code

@olucasfreitas
Copy link
Contributor Author

@hunterkepley I just did some tests here, and it seems to be working after the fix, did you test it with the branch locally ?

@olucasfreitas olucasfreitas force-pushed the OCM-18900 branch 2 times, most recently from 96029c9 to a50904c Compare November 3, 2025 19:49
@olucasfreitas
Copy link
Contributor Author

/retest-required

1 similar comment
@olucasfreitas
Copy link
Contributor Author

/retest-required

@olucasfreitas olucasfreitas force-pushed the OCM-18900 branch 2 times, most recently from 9502aa4 to 2637639 Compare November 5, 2025 15:38
@olucasfreitas
Copy link
Contributor Author

olucasfreitas commented Nov 11, 2025

@hunterkepley My bad then, I understood this more as as a suggestions that a thing to initiate a discussion. Answering what you said, I think we can show the password, because this way the user can just do a quick comparison and figure out what's wrong, WDYT ?

I'm also working on the test cases you commented

@olucasfreitas olucasfreitas force-pushed the OCM-18900 branch 2 times, most recently from dca6669 to 012f289 Compare November 12, 2025 19:06
@openshift-ci
Copy link
Contributor

openshift-ci bot commented Nov 12, 2025

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: olucasfreitas
Once this PR has been reviewed and has the lgtm label, please assign yuwang-rh for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@olucasfreitas olucasfreitas force-pushed the OCM-18900 branch 4 times, most recently from 560e392 to 3df4265 Compare November 13, 2025 19:27
@hunterkepley
Copy link
Contributor

/test all

@hunterkepley
Copy link
Contributor

/test e2e-presubmits-pr-rosa-sts-advanced

@hunterkepley
Copy link
Contributor

/test e2e-presubmits-pr-rosa-hcp-advanced

@hunterkepley
Copy link
Contributor

Some reason the e2e tests didn't run, I manually ran them so we can verify this MR didn't break any

@olucasfreitas
Copy link
Contributor Author

/retest

2 similar comments
@olucasfreitas
Copy link
Contributor Author

/retest

@olucasfreitas
Copy link
Contributor Author

/retest

Comment on lines 92 to 94
if parsedUri == nil {
return nil
}
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 I've left 2 comments now asking about this 🤔 ; but why did you add this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added that because _isUrl can return nil when it gets an empty string or nil input, and without that check we'd maybe get a panic, so I wanted to be sure, sorry about this, as I got a lot of comments I must have let this one pass, just removed it on the last commit

Copy link
Contributor

@hunterkepley hunterkepley Nov 19, 2025

Choose a reason for hiding this comment

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

@olucasfreitas Maybe it would be helpful to respond + resolve to comments one by one 🙂

But, I feel like something was misunderstood about the function based on the implementation of the nil check here

Here is the function you added this to:

func IsURLHttps(val interface{}) error {
	parsedUri, err := _isUrl(val)
	if err != nil {
		return err
	}
	if parsedUri.Scheme != helper.ProtocolHttps {
		return fmt.Errorf("expect URL '%s' to use an 'https://' scheme", val.(string))
	}
	return nil
}

So, the point of this, is to return an error when it's not valid, and return nil when it is valid.

Lets review where this function is called:

issuerUrl, err := interactive.GetString(interactive.Input{
			Question:   "Issuer URL (please include 'https://')",
			Help:       cmd.Flags().Lookup(IssuerUrlFlag).Usage,
			Required:   true,
			Validators: []interactive.Validator{interactive.IsURLHttps},
		})

Here it is used as a validator. The value is Required, meaning we cannot pass in an empty string.

The second place it's called is when validating another interactive prompt, but in a different way

If we look at the implementation of _isUrl, we can see the following:

func _isUrl(val interface{}) (*url.URL, error) {
	if val == nil {
		return nil, nil
	}
	s, ok := val.(string)
	if !ok {
		return nil, fmt.Errorf("can only validate strings, got %v", val)
	}
	if s == "" {
		return nil, nil
	}
	parsedUri, err := url.ParseRequestURI(fmt.Sprintf("%v", val))
	return parsedUri, err
}

So, which cases does this return nil?

  1. When the URL passed in is nil (impossible for strings! it's possible with pointers to strings, but not strings)
  2. When the URL is empty (not possible with the shown implementation)
  3. When there is a non-string passed in (this will be caught by the function already, and returned, before it checks if it's nil, due to it returning an actual error)

All implementations have been around since 2023, and are not related to this feature 🙂 so we should leave those functions alone. They only deal with OIDC providers, not create/cluster like this ticket is supposed to

Copy link
Contributor

Choose a reason for hiding this comment

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

But, I feel like something was misunderstood about the function based on the implementation of the nil check here

To circle it back to my original statement; If the URL was nil, I don't think we should return nil as the error. I think we should inform that the URL passed in was nil, and therefore incorrect. But again, we don't need any of it for this feature 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry about the comments, I had understood that the person that commented would resolve the comments if in the next review they saw that the issues we're resolved

also sorry about not getting this at the first place, thanks for the run down, I think my last commit is now correct

Copy link
Contributor

Choose a reason for hiding this comment

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

@olucasfreitas

Sorry about the comments, I had understood that the person that commented would resolve the comments if in the next review they saw that the issues we're resolved

I was replying to your original reply: as I got a lot of comments I must have let this one pass, just removed it on the last commit

it's all good if you leave the comments for the reviewer to resolve, in fact that's a good idea. What I was getting at was that it may be helpful to take them one at a time, and reply to it after fixing it. I see usually that you respond to all of them after fixing everything, then post the commit along with the replies is the reason I suggested it 🙂 might help break up the context of each review comment some

@hunterkepley
Copy link
Contributor

/test e2e-presubmits-pr-rosa-hcp-advanced

@hunterkepley
Copy link
Contributor

/test e2e-presubmits-pr-rosa-sts-advanced

@olucasfreitas olucasfreitas force-pushed the OCM-18900 branch 2 times, most recently from 34c6edf to 875a547 Compare November 19, 2025 19:47
@openshift-ci
Copy link
Contributor

openshift-ci bot commented Nov 19, 2025

@olucasfreitas: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/coverage 2637639 link true /test coverage

Full PR test history. Your PR dashboard.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@olucasfreitas
Copy link
Contributor Author

/retest

Field string
InvalidChar rune
HasInvalid bool
}
Copy link
Contributor

Choose a reason for hiding this comment

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

we should define structs at the top of files to be consistent with other files

I also suggest maybe putting this + the function below into a more specific file for its purpose (maybe pkg/helpers/url/validation.go would suite this nicely, or some variation of this)

return &URLCredentialValidation{
Field: field,
InvalidChar: char,
HasInvalid: true,
Copy link
Contributor

Choose a reason for hiding this comment

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

we could drop this field, and instead just check if InvalidChar is an empty rune or not

Copy link
Contributor

Choose a reason for hiding this comment

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

WDYT?

return checkForInvalidChars(password, "password")
}

func checkForInvalidChars(value, field string) *URLCredentialValidation {
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 this should also go in that new file dedicated to validating URLs

HasInvalid bool
}

func ValidateURLCredentials(urlString string) *URLCredentialValidation {
Copy link
Contributor

Choose a reason for hiding this comment

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

same for this


schemeIdx := strings.Index(urlString, "://")
if schemeIdx == -1 {
return &URLCredentialValidation{HasInvalid: false}
Copy link
Contributor

Choose a reason for hiding this comment

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

We should probably tell the user what is wrong here, right? This only says there is an invalid character somewhere

Comment on lines +153 to +158
if parsedURL.Scheme == "" {
return fmt.Errorf("invalid %s value: URL scheme is missing (e.g., %s://)", proxyType, expectedScheme)
}
if parsedURL.Scheme != expectedScheme {
return errors.Errorf("%s", fmt.Sprintf("expected %s to have an %s:// scheme", proxyType, expectedScheme))
}
Copy link
Contributor

Choose a reason for hiding this comment

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

We should encase the %s's in single quotes, and get rid of this first "empty check" validation. They are one and the same if we use single quotes. WDYT? Does that make sense?


// Proxy URLs shouldn't have a path
if parsedURL.Path != "" && parsedURL.Path != "/" {
return fmt.Errorf("invalid %s value: proxy URL should not contain a path", proxyType)
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we should let the user know what the value was in case they are confused?

Comment on lines +170 to +173
host, port, err := net.SplitHostPort(parsedURL.Host)
if err != nil {
// No port specified, just validate the host
host = parsedURL.Host
Copy link
Contributor

Choose a reason for hiding this comment

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

Are we sure that the only reason this can return an error is due to not having a port specified? I would suggest looking at the source code for that net pkg func

Comment on lines +176 to +178
portNum, _ := strconv.Atoi(port)
if portNum < 1 || portNum > 65535 {
return fmt.Errorf("invalid %s value: port must be between 1 and 65535", proxyType)
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 we need this validation specifically- the user shouldn't be inputting a giant or negative port number if they have a proxy URL they know they want to use. Having it result in a 404 eventually would be fine UX. WDYT?

Copy link
Contributor

@hunterkepley hunterkepley Dec 2, 2025

Choose a reason for hiding this comment

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

though there's no harm in keeping it specifically, it just strays some from the original goal

Comment on lines +186 to +189
// Not an IP, check if it's a valid hostname
if strings.Contains(host, "/") {
return fmt.Errorf("invalid %s value: invalid host", proxyType)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

is this needed? How do we know it was a slash that caused this? What happens if it wasn't a slash?

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.

4 participants