Skip to content

Conversation

@dtrifiro
Copy link

@dtrifiro dtrifiro commented Oct 17, 2025

The current implementation is broken if run in a cluster.

It should be possible to load an in-cluster configuration via kubernetes.config.load_incluster_config(), which populates
host/ports/certs from environment variables and secrets from /var/run/secrets/kubernetes.io.

There's a few issues in the current implementation:

  • Running get_client() falls through new_client_from_config which raises ConfigException: Invalid kube-config file. No configuration found.,
  • The final return is broken, since the DynamicClient is instantiated with client=kubernetes.config.incluster_config.load_incluster_config, which is always None
  • Trying to bypass the above problems by creating a custom client_config and passing it to get_client also fails: it falls through to new_client_from_config and raises ConfigException, because load_incluster_config sets client_config.api_key="bearer <token>" which is ignored.

This PR fixes the issues by:

  • only attempting to load the configuration file if passed explicitly via arg, explicitly set via KUBECONFIG env var or if ~/.kube.config exists.
  • loading the incluster config if in a k8s environment (KUBERNETES_SERVICE_PORT and KUBERNETES_SERVICE_HOST are set)
  • fall through passing the client_configuration as is, fixing the last issue.

Summary by CodeRabbit

  • New Features

    • Added automatic in-cluster configuration detection and support for KUBECONFIG environment variable.
  • Bug Fixes

    • Improved error handling for Kubernetes client initialization with streamlined configuration loading.
  • Chores

    • Enhanced logging for configuration selection and initialization processes.

@coderabbitai
Copy link

coderabbitai bot commented Oct 17, 2025

Walkthrough

get_client in ocp_resources/resource.py was reworked to prefer in-cluster loading when in-cluster environment variables are present (calls kubernetes.config.load_incluster_config and builds an ApiClient), adds a config-file path via kubernetes.config.new_client_from_config (supporting config_file, config_dict, KUBECONFIG), removes the MaxRetryError fallback, and returns a DynamicClient from the constructed client; informational logs were added.

Changes

Cohort / File(s) Summary
Client acquisition path rework
ocp_resources/resource.py
Prefer in-cluster loading when env vars indicate in-cluster; call kubernetes.config.load_incluster_config and construct an ApiClient; add config-file path via kubernetes.config.new_client_from_config (supports config_file, config_dict, KUBECONFIG) and log chosen path; remove MaxRetryError import and the retry-based fallback; always return a DynamicClient from the constructed client.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title "fix: incluster config handling" is fully related to the main changes in the pull request. The raw summary confirms that the primary modifications address in-cluster configuration handling by adding an incluster_config pathway, removing a problematic fallback, and improving the config loading logic. The title is concise, clear, and uses conventional commit syntax, making it easy for teammates to understand the primary change from commit history.
Description Check ✅ Passed The pull request description provides substantial and relevant content addressing the core issues and solutions, though it does not strictly adhere to all template section headings. The description clearly explains the problem (in-cluster config handling fails), documents the specific issues encountered, and details how the PR fixes them. While the description lacks explicit section headers for "Short description," "Which issue(s) this PR fixes," "Special notes for reviewer," and a "Bug:" section, the critical information—why the change is needed and what the PR does—is well-articulated and complete.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@redhat-qe-bot
Copy link
Contributor

Report bugs in Issues

Welcome! 🎉

This pull request will be automatically processed with the following features:

🔄 Automatic Actions

  • Reviewer Assignment: Reviewers are automatically assigned based on the OWNERS file in the repository root
  • Size Labeling: PR size labels (XS, S, M, L, XL, XXL) are automatically applied based on changes
  • Issue Creation: A tracking issue is created for this PR and will be closed when the PR is merged or closed
  • Pre-commit Checks: pre-commit runs automatically if .pre-commit-config.yaml exists
  • Branch Labeling: Branch-specific labels are applied to track the target branch
  • Auto-verification: Auto-verified users have their PRs automatically marked as verified

📋 Available Commands

PR Status Management

  • /wip - Mark PR as work in progress (adds WIP: prefix to title)
  • /wip cancel - Remove work in progress status
  • /hold - Block PR merging (approvers only)
  • /hold cancel - Unblock PR merging
  • /verified - Mark PR as verified
  • /verified cancel - Remove verification status

Review & Approval

  • /lgtm - Approve changes (looks good to me)
  • /approve - Approve PR (approvers only)
  • /automerge - Enable automatic merging when all requirements are met (maintainers and approvers only)
  • /assign-reviewers - Assign reviewers based on OWNERS file
  • /assign-reviewer @username - Assign specific reviewer
  • /check-can-merge - Check if PR meets merge requirements

Testing & Validation

  • /retest tox - Run Python test suite with tox
  • /retest python-module-install - Test Python package installation
  • /retest conventional-title - Validate commit message format
  • /retest all - Run all available tests

Container Operations

  • /build-and-push-container - Build and push container image (tagged with PR number)
    • Supports additional build arguments: /build-and-push-container --build-arg KEY=value

Cherry-pick Operations

  • /cherry-pick <branch> - Schedule cherry-pick to target branch when PR is merged
    • Multiple branches: /cherry-pick branch1 branch2 branch3

Label Management

  • /<label-name> - Add a label to the PR
  • /<label-name> cancel - Remove a label from the PR

✅ Merge Requirements

This PR will be automatically approved when the following conditions are met:

  1. Approval: /approve from at least one approver
  2. LGTM Count: Minimum 0 /lgtm from reviewers
  3. Status Checks: All required status checks must pass
  4. No Blockers: No WIP, hold, or conflict labels
  5. Verified: PR must be marked as verified (if verification is enabled)

📊 Review Process

Approvers and Reviewers

Approvers:

  • myakove
  • rnetser

Reviewers:

  • dbasunag
  • myakove
  • rnetser
Available Labels
  • hold
  • verified
  • wip
  • lgtm
  • approve
  • automerge

💡 Tips

  • WIP Status: Use /wip when your PR is not ready for review
  • Verification: The verified label is automatically removed on each new commit
  • Cherry-picking: Cherry-pick labels are processed when the PR is merged
  • Container Builds: Container images are automatically tagged with the PR number
  • Permission Levels: Some commands require approver permissions
  • Auto-verified Users: Certain users have automatic verification and merge privileges

For more information, please refer to the project documentation or contact the maintainers.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
ocp_resources/resource.py (1)

292-292: Prefer imported name over fully qualified reference.

Use the imported DynamicClient name instead of kubernetes.dynamic.DynamicClient for consistency with the import at line 22.

-    return kubernetes.dynamic.DynamicClient(client=_client)
+    return DynamicClient(client=_client)
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e432ad4 and 36adcfe.

📒 Files selected for processing (1)
  • ocp_resources/resource.py (1 hunks)

Pass either config_file or config_dict.
If none of them are passed, client will be created from default OS kubeconfig
If none of them are passed, client will be created from the incluster config or from default OS kubeconfig
Copy link
Collaborator

Choose a reason for hiding this comment

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

incluster should be called in the user didn't pass also username,password,host or token. Please update the docstring.

Copy link
Author

Choose a reason for hiding this comment

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

I refactored the logic to work in all broken cases, I also updated the docstring to reflect what's going on in the code

@myakove
Copy link
Collaborator

myakove commented Oct 19, 2025

/retest all

The current implementation is broken if run in a cluster.

It should be possible to load an in-cluster configuration via `kubernetes.config.load_incluster_config()`, which populates
host/ports/certs from environment variables and secrets from `/var/run/secrets/kubernetes.io`.

There's a few issues in the current implementation:
- Running `get_client()` falls through `new_client_from_config` which raises `ConfigException: Invalid kube-config file. No configuration found.`,
- The final return is broken, since the `DynamicClient` is
  instantiated with `client=kubernetes.config.incluster_config.load_incluster_config`, which is always None
- Trying to bypass the above problems by creating a custom `client_config` and passing it to `get_client` also fails: it falls through to
  `new_client_from_config` and raises `ConfigException`, because `load_incluster_config` sets `api_key="bearer <token>"` which is ignored.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
ocp_resources/resource.py (2)

241-260: Fix fall-through, honor client_configuration, and use try_refresh_token for in-cluster

  • _client can be undefined when not in-cluster and no kubeconfig is present.
  • Provided client_configuration isn’t used to build the client.
  • try_refresh_token parameter is unused; pass it to load_incluster_config.

Apply this patch:

@@
-    client_configuration = client_configuration or kubernetes.client.Configuration()
+    client_configuration = client_configuration or kubernetes.client.Configuration()
+    _client: kubernetes.client.ApiClient | None = None
@@
-    elif config_file or ("KUBECONFIG" in os.environ) or os.path.exists(os.path.expanduser("~/.kube/config")):
+    elif config_file or ("KUBECONFIG" in os.environ) or os.path.exists(os.path.expanduser("~/.kube/config")):
@@
-        if not config_file:
-            config_file = os.environ.get("KUBECONFIG", "~/.kube/config")
+        if not config_file:
+            config_file = os.environ.get("KUBECONFIG", "~/.kube/config")
+        # Ensure user home shortcut is expanded
+        config_file = os.path.expanduser(config_file)
         LOGGER.info("Trying to get client via new_client_from_config config_file=%s", config_file)
@@
-    elif all(
+    elif (client_configuration.api_key or client_configuration.host):
+        LOGGER.info("Using provided client_configuration to create ApiClient")
+        _client = kubernetes.client.ApiClient(client_configuration)
+    elif all(
         var in os.environ
         for var in (
             kubernetes.config.incluster_config.SERVICE_HOST_ENV_NAME,
             kubernetes.config.incluster_config.SERVICE_PORT_ENV_NAME,
         )
     ):
         LOGGER.info("Trying to get client via incluster_config")
 
-        # Ref: https://github.com/kubernetes-client/python/blob/v26.1.0/kubernetes/base/config/incluster_config.py
-        kubernetes.config.load_incluster_config(client_configuration)
+        # Ref: https://github.com/kubernetes-client/python/blob/v26.1.0/kubernetes/base/config/incluster_config.py
+        kubernetes.config.load_incluster_config(config=client_configuration, try_refresh_token=try_refresh_token)
         _client = kubernetes.client.ApiClient(client_configuration)
@@
-    return kubernetes.dynamic.DynamicClient(client=_client)
+    if _client is None:
+        raise kubernetes.config.config_exception.ConfigException(
+            "No Kubernetes configuration found. Provide credentials, config_dict/file, "
+            "a prepared client_configuration, or run in-cluster."
+        )
+    return kubernetes.dynamic.DynamicClient(client=_client)

This preserves intended precedence, prevents UnboundLocalError, and uses try_refresh_token. Based on learnings.

Also applies to: 270-285, 286-301


247-250: Don’t log proxy URL values (may leak credentials)

Logging the full proxy can expose user:pass in logs. Log the presence, not the value.

Apply this:

-        LOGGER.info(f"Setting proxy from environment variable: {proxy}")
+        LOGGER.info("Setting proxy from environment variable.")
🧹 Nitpick comments (1)
ocp_resources/resource.py (1)

206-236: Docstring: clarify precedence and document client_configuration usage

  • Precedence should be explicit and match code.
  • Document how a provided client_configuration is used.

Apply this docstring tweak:

@@
-    Valid combinations for arguments include:
-        - config file (kubeconfig path)
-        - config_dict
-        - username, password and host (uses basic auth)
-        - host and token (bearer token)
-        - client_configuration
+    Valid combinations for arguments include (first match wins):
+        1) username, password and host (basic auth)
+        2) host and token (bearer token)
+        3) config_dict
+        4) config file (kubeconfig path)
+        5) client_configuration (pre-configured kubernetes.client.Configuration with host/api_key)
@@
-    If none of the above arguments are provided:
-    - if the `KUBECONFIG` env var is set: it will be used to configure the client
-    - if not set and `~/.kube/config` exists, it will be used to configure the client
-
-    If no configuration files are available, incluster config loading will be attempted
+    If none of the above are provided:
+    - If the `KUBECONFIG` env var is set, it will be used.
+    - Else, if `~/.kube/config` exists, it will be used.
+    - Else, if running in a cluster (`KUBERNETES_SERVICE_HOST` and `KUBERNETES_SERVICE_PORT`), in‑cluster config is loaded.
+    - Otherwise, an explicit configuration is required.
@@
-        persist_config (bool): whether to persist config file.
+        persist_config (bool): whether to persist config file.
@@
-        token (str): Use token to login
+        token (str): Use token to login
+        client_configuration (kubernetes.client.Configuration | None): Pre-configured client configuration
+            (used directly if it contains required auth/host).

Based on learnings.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f99c070 and ff7f058.

📒 Files selected for processing (1)
  • ocp_resources/resource.py (2 hunks)

# Ref: https://github.com/kubernetes-client/python/blob/v26.1.0/kubernetes/base/config/__init__.py
LOGGER.info("Trying to get client via new_client_from_config")

elif config_file or ("KUBECONFIG" in os.environ) or os.path.exists(os.path.expanduser("~/.kube/config")):
Copy link
Collaborator

Choose a reason for hiding this comment

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

("KUBECONFIG" in os.environ) this is not a good check, this will be true for TEST_KUBECONFIG in os.environ.
Use os.environ.get("KUBECONFIG") and save it since you need to use it later.

Copy link
Author

Choose a reason for hiding this comment

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

TEST_KUBECONFIG as an environment variable? In that case "KUBECONFIG" in os.environ will not be true, os.environ is a dict and will not have the "KUBECONFIG" key.

The idea here is to go through this path only if:

  • config_file is passed by the user explicitly
  • user explicitly set a KUBECONFIG environment variable (so the key will be in os.environ)
  • a kubeconfig is available at the default location

# Ref: https://github.com/kubernetes-client/python/blob/v26.1.0/kubernetes/base/config/__init__.py
if not config_file:
config_file = os.environ.get("KUBECONFIG", "~/.kube/config")
LOGGER.info("Trying to get client via new_client_from_config config_file=%s", config_file)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please use f-string and not %s

Copy link
Author

Choose a reason for hiding this comment

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

This is the correct way to pass argument to logging statements: formatting of message arguments is deferred until it cannot be avoided.

https://docs.python.org/3/howto/logging.html#optimization
https://docs.python.org/3/howto/logging.html#logging-variable-data

client_configuration=client_configuration,
persist_config=persist_config,
)
elif all(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Any reason why not query os.environ with the needed keys?

Copy link
Author

@dtrifiro dtrifiro Oct 21, 2025

Choose a reason for hiding this comment

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

This is supposed to be a minimal check to assess whether this is running in a k8s environment, where KUBERNETES_SERVICE_PORT and KUBERNETES_SERVICE_HOST are set.

If these are set, there's a fairly good chance an incluster configuration is available, so we call load_incluster_config which will take care of reading/validating the configuration from env vars and files, see https://github.com/kubernetes-client/python/blob/a486091a91fdd0d0d389bc9538533118ee6fa3c8/kubernetes/base/config/incluster_config.py#L59-L85

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants