Skip to content

Commit 643eb7d

Browse files
committed
[aks-agent] Separate Azure OpenAI providers and add --yes flag
- Split Azure OpenAI provider into two separate providers: - Azure OpenAI (API Key): Requires API key authentication - Azure OpenAI (Microsoft Entra ID): Supports keyless authentication - Add role assignment reminders in helm deployment for keyless auth - Add --yes/-y flag to agent-cleanup command to skip confirmation - Change secret empty data message from warning to debug level - Bump to v0.7.1 (suppress litellm debug logs)
1 parent 5b6cf68 commit 643eb7d

File tree

8 files changed

+117
-22
lines changed

8 files changed

+117
-22
lines changed

src/aks-agent/HISTORY.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ Pending
1414

1515
1.0.0b22
1616
++++++++
17-
* Feature: Add Microsoft Entra ID (formerly Azure AD) authentication support for Azure OpenAI
17+
* Bump aks-agent to v0.7.1
18+
* Suppress litellm debug logs
19+
* Feature: Separate Azure OpenAI provider into API Key and Microsoft Entra ID (keyless) providers
20+
* Feature: Add --yes/-y flag to agent-cleanup command to skip confirmation prompt
1821

1922
1.0.0b21
2023
++++++++

src/aks-agent/azext_aks_agent/_consts.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
AKS_MCP_LABEL_SELECTOR = "app.kubernetes.io/name=aks-mcp"
5353

5454
# AKS Agent Version (shared by helm chart and docker image)
55-
AKS_AGENT_VERSION = "0.7.0"
55+
AKS_AGENT_VERSION = "0.7.1"
5656

5757
# Helm Configuration
5858
HELM_VERSION = "3.16.0"

src/aks-agent/azext_aks_agent/_params.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,9 @@ def load_arguments(self, _):
111111
help="The mode decides how the agent is deployed.",
112112
default="cluster",
113113
)
114+
c.argument(
115+
"yes",
116+
options_list=["--yes", "-y"],
117+
action="store_true",
118+
help="Do not prompt for confirmation.",
119+
)

src/aks-agent/azext_aks_agent/agent/k8s/aks_agent_manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ def _populate_api_keys_from_secret(self):
232232
)
233233

234234
if not secret.data:
235-
logger.warning("Secret '%s' exists but has no data", self.llm_secret_name)
235+
logger.debug("Secret '%s' exists but has no data", self.llm_secret_name)
236236
return
237237

238238
# Decode secret data (base64 encoded)

src/aks-agent/azext_aks_agent/agent/llm_providers/__init__.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55

66
from typing import List, Tuple
77

8-
from azext_aks_agent.agent.console import ERROR_COLOR, HELP_COLOR
8+
from azext_aks_agent.agent.console import ERROR_COLOR, HELP_COLOR, INFO_COLOR
99
from rich.console import Console
1010

1111
from .anthropic_provider import AnthropicProvider
1212
from .azure_provider import AzureProvider
13+
from .azure_entraid_provider import AzureEntraIDProvider
1314
from .base import LLMProvider
1415
from .gemini_provider import GeminiProvider
1516
from .openai_compatible_provider import OpenAICompatibleProvider
@@ -19,11 +20,11 @@
1920

2021
_PROVIDER_CLASSES: List[LLMProvider] = [
2122
AzureProvider,
23+
AzureEntraIDProvider,
2224
OpenAIProvider,
2325
AnthropicProvider,
2426
GeminiProvider,
2527
OpenAICompatibleProvider,
26-
# Add new providers here
2728
]
2829

2930
PROVIDER_REGISTRY = {}
@@ -49,8 +50,9 @@ def _get_provider_by_index(idx: int) -> LLMProvider:
4950
Raises ValueError if index is out of range.
5051
"""
5152
if 1 <= idx <= len(_PROVIDER_CLASSES):
52-
console.print("You selected provider:", _PROVIDER_CLASSES[idx - 1]().readable_name, style=f"bold {HELP_COLOR}")
53-
return _PROVIDER_CLASSES[idx - 1]()
53+
provider = _PROVIDER_CLASSES[idx - 1]()
54+
console.print("You selected provider:", provider.readable_name, style=f"bold {HELP_COLOR}")
55+
return provider
5456
raise ValueError(f"Invalid provider index: {idx}")
5557

5658

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
6+
7+
from typing import Tuple
8+
9+
from .base import LLMProvider, is_valid_url, non_empty
10+
11+
12+
def is_valid_api_base(v: str) -> bool:
13+
if not v.startswith("https://"):
14+
return False
15+
return is_valid_url(v)
16+
17+
18+
class AzureEntraIDProvider(LLMProvider):
19+
@property
20+
def readable_name(self) -> str:
21+
return "Azure OpenAI (Microsoft Entra ID)"
22+
23+
@property
24+
def model_route(self) -> str:
25+
return "azure"
26+
27+
@property
28+
def parameter_schema(self):
29+
return {
30+
"model": {
31+
"secret": False,
32+
"default": None,
33+
"hint": "ensure your deployment name is the same as the model name, e.g., gpt-5",
34+
"validator": non_empty,
35+
"alias": "deployment_name"
36+
},
37+
"api_base": {
38+
"secret": False,
39+
"default": None,
40+
"validator": is_valid_api_base
41+
},
42+
"api_version": {
43+
"secret": False,
44+
"default": "2025-04-01-preview",
45+
"hint": None,
46+
"validator": non_empty
47+
}
48+
}
49+
50+
def validate_connection(self, params: dict) -> Tuple[str, str]:
51+
api_base = params.get("api_base")
52+
api_version = params.get("api_version")
53+
deployment_name = params.get("model")
54+
55+
if not all([api_base, api_version, deployment_name]):
56+
return "Missing required Azure parameters.", "retry_input"
57+
58+
return None, "save"

src/aks-agent/azext_aks_agent/agent/llm_providers/azure_provider.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def is_valid_api_base(v: str) -> bool:
2424
class AzureProvider(LLMProvider):
2525
@property
2626
def readable_name(self) -> str:
27-
return "Azure OpenAI"
27+
return "Azure OpenAI (API Key)"
2828

2929
@property
3030
def model_route(self) -> str:
@@ -43,8 +43,8 @@ def parameter_schema(self):
4343
"api_key": {
4444
"secret": True,
4545
"default": None,
46-
"hint": "press enter to enable keyless authentication with Microsoft Entra ID",
47-
"validator": lambda v: True
46+
"hint": None,
47+
"validator": non_empty
4848
},
4949
"api_base": {
5050
"secret": False,
@@ -65,12 +65,9 @@ def validate_connection(self, params: dict) -> Tuple[str, str]:
6565
api_version = params.get("api_version")
6666
deployment_name = params.get("model")
6767

68-
if not all([api_base, api_version, deployment_name]):
68+
if not all([api_key, api_base, api_version, deployment_name]):
6969
return "Missing required Azure parameters.", "retry_input"
7070

71-
if not api_key or not api_key.strip():
72-
return None, "save"
73-
7471
# REST API reference: https://learn.microsoft.com/en-us/azure/ai-foundry/openai/api-version-lifecycle?tabs=rest
7572
url = urljoin(api_base, f"openai/deployments/{deployment_name}/chat/completions")
7673

src/aks-agent/azext_aks_agent/custom.py

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from azext_aks_agent.agent.k8s import AKSAgentManager, AKSAgentManagerClient
2020
from azext_aks_agent.agent.k8s.aks_agent_manager import AKSAgentManagerLLMConfigBase
2121
from azext_aks_agent.agent.llm_providers import prompt_provider_choice
22+
from azext_aks_agent.agent.llm_providers.azure_entraid_provider import AzureEntraIDProvider
2223
from azext_aks_agent.agent.telemetry import CLITelemetryClient
2324
from azure.cli.core.azclierror import AzCLIError
2425
from azure.cli.core.commands.client_factory import get_subscription_id
@@ -178,6 +179,19 @@ def _setup_helm_deployment(console, aks_agent_manager: AKSAgentManager):
178179
console.print(
179180
f"\n👤 Current service account in namespace '{aks_agent_manager.namespace}': {service_account_name}",
180181
style="cyan")
182+
183+
# Check if using Azure Entra ID provider and show role assignment reminder
184+
model_list = aks_agent_manager.get_llm_config()
185+
if model_list and any("azure/" in model_name and not model_config.get("api_key") for model_name, model_config in model_list.items()):
186+
console.print(
187+
f"\n⚠️ IMPORTANT: If using keyless authentication with Azure OpenAI, ensure the 'Cognitive Services OpenAI User' or 'Azure AI Developer' role "
188+
f"is assigned to the workload identity (service account: {service_account_name}).",
189+
style=f"bold {INFO_COLOR}"
190+
)
191+
console.print(
192+
"Learn more: https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/managed-identity\n",
193+
style=INFO_COLOR
194+
)
181195

182196
elif helm_status == "not_found":
183197
console.print(
@@ -196,6 +210,19 @@ def _setup_helm_deployment(console, aks_agent_manager: AKSAgentManager):
196210
"To have access to Azure resources, the service account should be annotated with "
197211
"'azure.workload.identity/client-id: <managed-identity-client-id>'.",
198212
style=WARNING_COLOR)
213+
214+
# Check if using Azure Entra ID provider and show role assignment note
215+
model_list = aks_agent_manager.get_llm_config()
216+
if model_list and any("azure/" in model_name and not model_config.get("api_key") for model_name, model_config in model_list.items()):
217+
console.print(
218+
"\n⚠️ NOTE: You are using keyless authentication with Azure OpenAI. "
219+
"Ensure the 'Cognitive Services OpenAI User' or 'Azure AI Developer' role is assigned to the workload identity.",
220+
style=f"bold {INFO_COLOR}"
221+
)
222+
console.print(
223+
"Learn more: https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/managed-identity",
224+
style=INFO_COLOR
225+
)
199226

200227
# Prompt user for service account name (required)
201228
while True:
@@ -422,6 +449,7 @@ def aks_agent_cleanup(
422449
cluster_name,
423450
namespace,
424451
mode=None,
452+
yes=False,
425453
):
426454
"""Cleanup and uninstall the AKS agent."""
427455
with CLITelemetryClient(event_type="cleanup") as telemetry_client:
@@ -442,16 +470,17 @@ def aks_agent_cleanup(
442470
f"⚠️ Warning: --namespace '{namespace}' is specified but will be ignored in client mode.",
443471
style=WARNING_COLOR)
444472

445-
console.print(
446-
"\n⚠️ Warning: This will uninstall the AKS agent and delete all associated resources.",
447-
style=WARNING_COLOR)
473+
if not yes:
474+
console.print(
475+
"\n⚠️ Warning: This will uninstall the AKS agent and delete all associated resources.",
476+
style=WARNING_COLOR)
448477

449-
user_confirmation = console.input(
450-
f"\n[{WARNING_COLOR}]Are you sure you want to proceed with cleanup? (y/N): [/]").strip().lower()
478+
user_confirmation = console.input(
479+
f"\n[{WARNING_COLOR}]Are you sure you want to proceed with cleanup? (y/N): [/]").strip().lower()
451480

452-
if user_confirmation not in ['y', 'yes']:
453-
console.print("❌ Cleanup cancelled.", style=INFO_COLOR)
454-
return
481+
if user_confirmation not in ['y', 'yes']:
482+
console.print("❌ Cleanup cancelled.", style=INFO_COLOR)
483+
return
455484

456485
console.print("\n🗑️ Starting cleanup (this typically takes a few seconds)...", style=INFO_COLOR)
457486

0 commit comments

Comments
 (0)