Skip to content

feat: add Windows PowerShell scripts for cloud-deployment example#900

Open
spandey1702 wants to merge 2 commits into
a2aproject:mainfrom
spandey1702:fix/windows-support-issue-395
Open

feat: add Windows PowerShell scripts for cloud-deployment example#900
spandey1702 wants to merge 2 commits into
a2aproject:mainfrom
spandey1702:fix/windows-support-issue-395

Conversation

@spandey1702
Copy link
Copy Markdown

Add deploy.ps1, cleanup.ps1, and verify.ps1 as Windows equivalents of the existing bash scripts. Fixes #395.

  • deploy.ps1: full cluster setup (Kind, registry, Strimzi, PostgreSQL, Kafka, agent) with -ContainerTool docker|podman parameter
  • cleanup.ps1: reverse-order teardown with confirmation prompt
  • verify.ps1: health checks for all deployed components

Key translation notes:

  • Bash heredocs -> PowerShell here-strings piped to kubectl/docker stdin
  • export VAR=val -> $env:VAR = 'val'
  • grep/|| true -> -match / $LASTEXITCODE checks
  • sleep N -> Start-Sleep -Seconds N
  • curl -> curl.exe (avoids PowerShell Invoke-WebRequest alias)
  • SKIP_ENTITY_OPERATOR_WAIT and SKIP_AGENT_DEPLOY env vars supported

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces Windows PowerShell equivalents (deploy.ps1, cleanup.ps1, and verify.ps1) for the existing bash deployment scripts, along with updated documentation in the README.md. The code review feedback identifies several critical issues in the PowerShell scripts, primarily concerning Windows compatibility. These include a syntax error with here-string redirection, missing dependency checks for Maven and the container tool, and potential failures in string comparisons due to trailing carriage returns (\r) in output captured from external CLI tools like kubectl, docker/podman, and kind.

Comment on lines +119 to +129
@"
apiVersion: v1
kind: ConfigMap
metadata:
name: local-registry-hosting
namespace: kube-public
data:
localRegistryHosting.v1: |
host: "localhost:${RegPort}"
help: "https://kind.sigs.k8s.io/docs/user/local-registry/"
"@ | kubectl apply -f -
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

In PowerShell, the closing delimiter of a here-string ("@) must be on its own line at the very beginning of the line, with no other characters following it. Piping directly from the closing delimiter on the same line causes a syntax error in Windows PowerShell. Assigning the here-string to a variable first resolves this issue.

$registryConfig = @"
apiVersion: v1
kind: ConfigMap
metadata:
  name: local-registry-hosting
  namespace: kube-public
data:
  localRegistryHosting.v1: |
    host: "localhost:${RegPort}"
    help: "https://kind.sigs.k8s.io/docs/user/local-registry/"
"@
$registryConfig | kubectl apply -f -

Comment on lines +57 to +58
$running = & $ContainerTool inspect -f '{{.State.Running}}' $RegName 2>$null
if ($running -ne "true") {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

On Windows, external CLI tools like Docker/Podman often output trailing carriage returns (\r). When PowerShell captures this output, $running will contain "true\r", making the exact string comparison $running -ne "true" evaluate to $true even when the container is running. This will cause the script to attempt to recreate the registry container, failing with a name conflict error. Using -notmatch "^true\s*$" makes the check robust against trailing whitespace and carriage returns.

$running = & $ContainerTool inspect -f '{{.State.Running}}' $RegName 2>$null
if ($running -notmatch "^true\s*$") {

Comment on lines +108 to +109
$networkInfo = & $ContainerTool inspect -f '{{json .NetworkSettings.Networks.kind}}' $RegName 2>$null
if ($networkInfo -eq "null" -or [string]::IsNullOrEmpty($networkInfo)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Similar to the registry running check, if $networkInfo contains a trailing carriage return ("null\r"), the exact comparison $networkInfo -eq "null" will evaluate to $false. This will cause the script to skip connecting the registry to the cluster network, leading to ImagePullBackOff errors when nodes try to pull images. Using -match "null" or trimming the string avoids this issue.

$networkInfo = & $ContainerTool inspect -f '{{json .NetworkSettings.Networks.kind}}' $RegName 2>$null
if ([string]::IsNullOrEmpty($networkInfo) -or $networkInfo -match "null") {

Comment on lines +44 to +49
# Check if kubectl is installed
if (-not (Get-Command kubectl -ErrorAction SilentlyContinue)) {
Write-Host "Error: kubectl is not installed" -ForegroundColor Red
Write-Host "Please install kubectl first: https://kubernetes.io/docs/tasks/tools/"
exit 1
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The script runs $ContainerTool and mvn without verifying if they are installed and available in the system's PATH. If they are missing, PowerShell will throw a CommandNotFoundException but may continue executing subsequent steps (since $ErrorActionPreference is not set to Stop), leading to silent failures or confusing errors. Adding explicit checks ensures the script fails fast with a clear error message.

# Check if kubectl is installed
if (-not (Get-Command kubectl -ErrorAction SilentlyContinue)) {
    Write-Host "Error: kubectl is not installed" -ForegroundColor Red
    Write-Host "Please install kubectl first: https://kubernetes.io/docs/tasks/tools/"
    exit 1
}

# Check if container tool is installed
if (-not (Get-Command $ContainerTool -ErrorAction SilentlyContinue)) {
    Write-Host "Error: $ContainerTool is not installed or not in PATH" -ForegroundColor Red
    exit 1
}

# Check if Maven is installed
if (-not (Get-Command mvn -ErrorAction SilentlyContinue)) {
    Write-Host "Error: Maven (mvn) is not installed" -ForegroundColor Red
    Write-Host "Please install Maven first: https://maven.apache.org/install.html"
    exit 1
}

Comment on lines +70 to +71
$clusters = kind get clusters 2>$null
if ($clusters -notmatch "(?m)^kind$") {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

In .NET regular expressions, $ matches before \n but does not match before \r. On Windows, kind get clusters may return "kind\r\n", which will fail to match (?m)^kind$. Adding \r? before $ makes the regex robust on Windows.

$clusters = kind get clusters 2>$null
if ($clusters -notmatch "(?m)^kind\r?$") {

Comment on lines +33 to +35
$postgresReady = kubectl get pods -n a2a-demo -l app=postgres `
-o "jsonpath={.items[0].status.conditions[?(@.type=='Ready')].status}" 2>$null
if ($postgresReady -eq "True") {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

On Windows, kubectl output captured by PowerShell may contain trailing carriage returns (\r). An exact comparison like $postgresReady -eq "True" will fail if the value is "True\r", incorrectly reporting that PostgreSQL is not ready. Using -match "^True\s*$" is much more robust.

$postgresReady = kubectl get pods -n a2a-demo -l app=postgres `
    -o "jsonpath={.items[0].status.conditions[?(@.type=='Ready')].status}" 2>$null
if ($postgresReady -match "^True\s*$") {

Comment on lines +46 to +48
$kafkaReady = kubectl get kafka a2a-kafka -n kafka `
-o "jsonpath={.status.conditions[?(@.type=='Ready')].status}" 2>$null
if ($kafkaReady -eq "True") {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Using -match "^True\s*$" ensures that trailing carriage returns (\r) from kubectl output do not cause the readiness check to fail incorrectly.

$kafkaReady = kubectl get kafka a2a-kafka -n kafka `
    -o "jsonpath={.status.conditions[?(@.type=='Ready')].status}" 2>$null
if ($kafkaReady -match "^True\s*$") {

Comment on lines +59 to +61
$agentStatusJson = kubectl get pods -n a2a-demo -l app=a2a-agent `
-o "jsonpath={range .items[*]}{.status.conditions[?(@.type=='Ready')].status}{'\n'}{end}" 2>$null
$agentReady = ($agentStatusJson -split "`n" | Where-Object { $_ -eq "True" }).Count
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

PowerShell automatically captures multi-line output from external commands as an array of strings, so splitting by \n is unnecessary. Furthermore, using -match "^True\s*$" instead of -eq "True" protects against trailing carriage returns (\r) on Windows.

$agentStatusJson = kubectl get pods -n a2a-demo -l app=a2a-agent `
    -o "jsonpath={range .items[*]}{.status.conditions[?(@.type=='Ready')].status}{'\n'}{end}" 2>$null
$agentReady = ($agentStatusJson | Where-Object { $_ -match "^True\s*$" }).Count

@spandey1702 spandey1702 changed the title Add Windows PowerShell scripts for cloud-deployment example (#395) feat: add Windows PowerShell scripts for cloud-deployment example May 27, 2026
…ct#395)

Add deploy.ps1, cleanup.ps1, and verify.ps1 as Windows equivalents
of the existing bash scripts. Fixes a2aproject#395.

- deploy.ps1: full cluster setup (Kind, registry, Strimzi, PostgreSQL,
  Kafka, agent) with -ContainerTool docker|podman parameter
- cleanup.ps1: reverse-order teardown with confirmation prompt
- verify.ps1: health checks for all deployed components

Key translation notes:
- Bash heredocs -> PowerShell here-strings piped to kubectl/docker stdin
- export VAR=val -> $env:VAR = 'val'
- grep/|| true -> -match / $LASTEXITCODE checks
- sleep N -> Start-Sleep -Seconds N
- curl -> curl.exe (avoids PowerShell Invoke-WebRequest alias)
- SKIP_ENTITY_OPERATOR_WAIT and SKIP_AGENT_DEPLOY env vars supported

Also updates README.md with Windows quick-start instructions,
PowerShell execution policy note, and updated project structure table.
@spandey1702 spandey1702 force-pushed the fix/windows-support-issue-395 branch from 14da7a7 to 58311ac Compare May 27, 2026 19:36
- Add missing dependency checks for Maven and container tool
- Trim docker/podman inspect output to handle CRLF on Windows
- Use \r? in kind cluster regex to match Windows line endings
- Use -match '^True\s*$' instead of -eq 'True' in verify.ps1
  to handle trailing whitespace/carriage returns in kubectl output
@spandey1702
Copy link
Copy Markdown
Author

Addressed all Gemini review comments — added .Trim() for CRLF handling, \r? in the kind cluster regex, -match "^True\s*$" in verify.ps1, and dependency checks for Maven and the container tool.

@kabir
Copy link
Copy Markdown
Collaborator

kabir commented May 29, 2026

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces Windows PowerShell scripts (deploy.ps1, cleanup.ps1, and verify.ps1) as equivalents to the existing bash scripts for the cloud deployment example, along with corresponding updates to the README.md. The review feedback highlights several critical issues in the new PowerShell scripts: incorrect package names for the test client in deploy.ps1 and verify.ps1 that would cause a ClassNotFoundException, potential runtime exceptions from calling .Trim() on null values when inspecting containers, and a lack of error checking after kubectl wait commands which allows the deployment script to silently continue upon failure.

Write-Host "To run the test client (demonstrating load balancing):"
Write-Host " cd ..\server"
Write-Host " mvn test-compile exec:java -Dexec.classpathScope=test ``"
Write-Host " -Dexec.mainClass=`"io.a2a.examples.cloud.A2ACloudExampleClient`" ``"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The package name for A2ACloudExampleClient is org.a2aproject.sdk.examples.cloud, not io.a2a.examples.cloud. Running the printed command with the incorrect package name will result in a ClassNotFoundException.

    -Dexec.mainClass=`"org.a2aproject.sdk.examples.cloud.A2ACloudExampleClient`" ``"

Write-Host "To run the test client (demonstrating load balancing):"
Write-Host " cd ..\server"
Write-Host " mvn test-compile exec:java -Dexec.classpathScope=test ``"
Write-Host " -Dexec.mainClass=`"io.a2a.examples.cloud.A2ACloudExampleClient`" ``"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The package name for A2ACloudExampleClient is org.a2aproject.sdk.examples.cloud, not io.a2a.examples.cloud. Running the printed command with the incorrect package name will result in a ClassNotFoundException.

    -Dexec.mainClass=`"org.a2aproject.sdk.examples.cloud.A2ACloudExampleClient`" ``"


# Create registry container if it doesn't exist
# .Trim() strips trailing \r\n from docker/podman output on Windows
$running = (& $ContainerTool inspect -f '{{.State.Running}}' $RegName 2>$null).Trim()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

If the registry container does not exist, the inspect command will fail and return $null. Calling .Trim() directly on $null will throw a PowerShell runtime exception: You cannot call a method on a null-valued expression.

Casting the result to [string] first ensures that a null value becomes an empty string, which can be safely trimmed without throwing an error.

$running = ([string](& $ContainerTool inspect -f '{{.State.Running}}' $RegName 2>$null)).Trim()

Write-Host ""
Write-Host "Connecting registry to cluster network..."
# .Trim() strips trailing \r\n from docker/podman output on Windows
$networkInfo = (& $ContainerTool inspect -f '{{json .NetworkSettings.Networks.kind}}' $RegName 2>$null).Trim()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

If the inspect command fails or returns no output, calling .Trim() on $null will throw a runtime exception. Casting the result to [string] first prevents this.

$networkInfo = ([string](& $ContainerTool inspect -f '{{json .NetworkSettings.Networks.kind}}' $RegName 2>$null)).Trim()

Comment on lines +228 to +230
kubectl wait --for=condition=Available deployment/strimzi-cluster-operator -n kafka --timeout=300s
kubectl wait --for=condition=Ready pod -l name=strimzi-cluster-operator -n kafka --timeout=300s
Write-Host "✓ Strimzi operator installed" -ForegroundColor Green
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

If the Strimzi operator fails to become ready within the timeout, the script will silently continue and fail later during Kafka deployment. It is better to check $LASTEXITCODE and exit early with a clear error message.

    kubectl wait --for=condition=Available deployment/strimzi-cluster-operator -n kafka --timeout=300s
    if ($LASTEXITCODE -ne 0) {
        Write-Host "ERROR: Strimzi operator deployment failed to become available" -ForegroundColor Red
        exit 1
    }
    kubectl wait --for=condition=Ready pod -l name=strimzi-cluster-operator -n kafka --timeout=300s
    if ($LASTEXITCODE -ne 0) {
        Write-Host "ERROR: Strimzi operator pod failed to become ready" -ForegroundColor Red
        exit 1
    }
    Write-Host "✓ Strimzi operator installed" -ForegroundColor Green

Comment on lines +246 to +247
kubectl wait --for=condition=Ready pod -l app=postgres -n a2a-demo --timeout=120s
Write-Host "✓ PostgreSQL deployed" -ForegroundColor Green
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

If PostgreSQL fails to become ready, the script should fail-fast instead of continuing to deploy Kafka and the agent.

kubectl wait --for=condition=Ready pod -l app=postgres -n a2a-demo --timeout=120s
if ($LASTEXITCODE -ne 0) {
    Write-Host "ERROR: PostgreSQL failed to become ready" -ForegroundColor Red
    exit 1
}
Write-Host "✓ PostgreSQL deployed" -ForegroundColor Green

Comment on lines +348 to +349
kubectl wait --for=condition=Ready pod -l app=a2a-agent -n a2a-demo --timeout=120s
Write-Host "✓ Agent deployed" -ForegroundColor Green
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

If the agent pods fail to become ready, the script should report an error and exit with a non-zero code.

    kubectl wait --for=condition=Ready pod -l app=a2a-agent -n a2a-demo --timeout=120s
    if ($LASTEXITCODE -ne 0) {
        Write-Host "ERROR: Agent pods failed to become ready" -ForegroundColor Red
        exit 1
    }
    Write-Host "✓ Agent deployed" -ForegroundColor Green

@kabir
Copy link
Copy Markdown
Collaborator

kabir commented May 29, 2026

Hi @spandey1702 thank you.

I think it could be good to have these scripts tested on CI, similar to what we do for Linux in https://github.com/a2aproject/a2a-java/blob/main/.github/workflows/cloud-deployment-example.yml

AIUI GH Actions also provides Windows runners.

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.

[Feat]: Set up Kubernetes example to also run on Windows.

2 participants