Skip to content

feat: Add address validation for gRPC connections and corresponding t… - #7117

Open
Anjali-Chauhan1 wants to merge 1 commit into
pipe-cd:masterfrom
Anjali-Chauhan1:feat/pipectl-validate-address-format
Open

feat: Add address validation for gRPC connections and corresponding t…#7117
Anjali-Chauhan1 wants to merge 1 commit into
pipe-cd:masterfrom
Anjali-Chauhan1:feat/pipectl-validate-address-format

Conversation

@Anjali-Chauhan1

Copy link
Copy Markdown

What this PR does:

Adds --address format validation to Options.Validate() in pkg/app/pipectl/client/client.go.

A new validateAddress helper mirrors grpc-go's own target parsing instead of inventing its own rules:

  • If the scheme is backed by a registered resolver (resolver.Get), the address is accepted and left to that resolver — endpoint syntax is resolver-specific, so dns:///localhost (defaults to :443) and unix:///var/run/pipecd.sock (no port at all) stay valid.
  • Otherwise the address goes to the default passthrough resolver and must be a dialable host:port: non-empty host, no whitespace, and a port resolving via net.LookupPort to 1–65535.

Using the resolver registry rather than a hardcoded scheme allowlist keeps this correct automatically — it rejects xds://, for example, which is a real gRPC scheme but is not linked into the pipectl binary.

Also adds pkg/app/pipectl/client/client_test.go. This package previously had no tests; the new file covers validateAddress, Options.Validate(), and a NewClient case asserting a bad address fails fast rather than burning the 5s dial timeout (37 subtests).

Why we need it:

Options.Validate() only checked that --address was non-empty. Any non-empty malformed value passed through to grpc.DialContext() and surfaced after the dial timeout as:

desc = "transport: Error while dialing: dial tcp: address localhost: missing port in address"

That is hard to act on, and Options is shared by every pipectl subcommand (application, deployment, encrypt, event, planpreview, transfer, plugin), so it affects all pipectl users.

Behaviour was verified empirically against grpc v1.79.3 rather than from the docs. Inputs now rejected up front:

input error
localhost must be in host:port form (e.g. localhost:9000) or use a gRPC scheme such as dns:///
http://localhost:9000 scheme "http" is not a supported gRPC resolver
tcp://localhost:9000 scheme "tcp" is not a supported gRPC resolver
localhost:9000/path "9000/path" is not a valid port
localhost:abc "abc" is not a valid port
localhost: and localhost:0 port must be between 1 and 65535
" localhost:9000" host must not contain whitespace
:9000 host must not be empty

The last three matter because the stdlib fails silently on them: net.SplitHostPort("localhost:") returns an empty port with no error, and net.LookupPort("tcp", "") returns 0, nil — so gRPC would happily dial port 0.

Which issue(s) this PR fixes:

Fixes #7089

This deviates from the approach proposed in the issue. That algorithm (SplitHostPort, falling back to url.Parse, then checking for a non-empty host or any scheme) scored 9/13 against real gRPC behaviour — it accepts http://…, tcp://…, localhost:9000/path, and localhost:abc, because SplitHostPort does not validate that the port is numeric and "has a scheme" is too weak a test. Its instinct to exempt resolver URIs from the port requirement was correct and is preserved here.

Does this PR introduce a user-facing change?:

Yes.

  • How are users affected by this change: A malformed --address now fails immediately with a specific message naming the problem and echoing the offending value, instead of a multi-line gRPC transport error after the dial timeout. Every previously valid form still works — host:port, IPv4, bracketed IPv6, FQDN, named ports (localhost:http), and dns:///, unix:///, unix:, passthrough:/// resolver URIs.
  • Is this breaking change: No. Validation was designed for zero false-rejects, and every newly rejected input was already non-functional. Two notes for reviewers: anyone parsing the old error text will see different strings, and an exotic target outside the tested set would now be blocked at validation rather than failing at dial.
  • How to migrate (if breaking change): N/A

…ests

Signed-off-by: Anjali-Chauhan1 <anjalichauhan1036@gmail.com>
@Anjali-Chauhan1
Anjali-Chauhan1 requested a review from a team as a code owner August 4, 2026 01:38
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

👋 Hi @Anjali-Chauhan1, welcome to PipeCD and thanks for opening your first pull request!

We’re really happy to have you here

Before your PR gets merged, please check a few important things below.


Helpful resources


DCO Sign-off

All commits must include a Signed-off-by line to comply with the Developer Certificate of Origin (DCO).

In case you forget to sign-off your commit(s), follow these steps:

For the last commit:

git commit --amend --signoff
git push --force-with-lease

For multiple commits:

git rebase --signoff origin/master
git push --force-with-lease

Run checks locally

Before pushing updates, please run:

make check

This runs the same checks as CI and helps catch issues early.


💬 Need help?

If anything is unclear, feel free to ask in this PR or join us on the CNCF Slack in the #pipecd channel.
You can get your Slack invite from: https://communityinviter.com/apps/cloud-native/cncf

Thanks for contributing to PipeCD! ❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Validate --address flag format in pipectl client options

1 participant