ronantakizawa/codereview-bench huggingface dataset:
Repository: GoogleCloudPlatform/kubectl-ai
File: install.sh (Shell)

Code before:
esac

# Get latest version tag from GitHub API (portable, no grep -P)
LATEST_TAG=$(curl -s "https://api.github.com/repos/$REPO/releases/latest" | sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p')
if [ -z "$LATEST_TAG" ]; then
  echo "Failed to fetch latest release tag."
  exit 1
fi

# Compose download URL
TARBALL="kubectl-ai_${OS}_${ARCH}.tar.gz"
URL="https://github.com/$REPO/releases/download/$LATEST_TAG/$TARBALL"

# Download and extract
echo "Downloading $URL ..."
curl -LO "$URL"
tar -xzf "$TARBALL"

# Move binary to /usr/local/bin (may require sudo)
echo "Installing $BINARY to /usr/local/bin (may require sudo)..."
sudo mv "$BINARY" /usr/local/bin/

# Ensure the binary is executable
sudo chmod +x /usr/local/bin/$BINARY

# Clean up
rm "$TARBALL"

echo "✅ $BINARY installed successfully! Run '$BINARY --help' to get started."



Review comment (bug):
One thing I noticed that the OS name in the tar file is capitalized `Darwin`, `Linux`, `Windows`, but here we will be using OS name all lower-cased. I wonder if it will work with curl on all OSes. Lets try to match them exact to avoid any edge cases.

I think this script is super important because failures here will frustrate the users and some of them may never bother to install and just abort it. So better to be safe than sorry.

Code after:
case "$ARCH" in
  x86_64|amd64) ARCH="x86_64" ;;
  arm64|aarch64) ARCH="arm64" ;;
  *)
    echo "If you are on an unsupported architecture, please follow the manual installation instructions at:"
    echo "https://github.com/GoogleCloudPlatform/kubectl-ai#manual-installation"
    exit 1
    ;;
esac

# Get latest version tag from GitHub API, Use GITHUB_TOKEN if available to avoid potential rate limit
if [ -n "${GITHUB_TOKEN:-}" ]; then
  auth_hdr="Authorization: token $GITHUB_TOKEN"
else
  auth_hdr=""
fi
LATEST_TAG=$(curl -s -H "$auth_hdr" \
  "https://api.github.com/repos/$REPO/releases/latest" \
  | sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p')
if [ -z "$LATEST_TAG" ]; then
  echo "Failed to fetch latest release tag."
  exit 1
fi

# Compose download URL
TARBALL="kubectl-ai_${OS}_${ARCH}.tar.gz"
URL="https://github.com/$REPO/releases/download/$LATEST_TAG/$TARBALL"

# Download and extract
echo "Downloading $URL ..."
curl -fSL --retry 3 "$URL" -o "$TARBALL"
tar --no-same-owner -xzf "$TARBALL"

# Move binary to /usr/local/bin (may require sudo)
echo "Installing $BINARY to /usr/local/bin (may require sudo)..."
sudo install -m 0755 "$BINARY" /usr/local/bin/

# Clean up
rm "$TARBALL"

echo "✅ $BINARY installed successfully! Run '$BINARY --help' to get started."
Repository: GoogleCloudPlatform/kubectl-ai
File: k8s-bench/tasks/fix-probes/setup.sh (Shell)

Code before:
      containers:
      - name: webapp
        image: nginx:latest
        ports:
        - containerPort: 80
        # The problem: incorrect health probes causing restarts
        livenessProbe:
          httpGet:
            path: /nonexistent-path  # Path doesn't exist
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /nonexistent-path  # Path doesn't exist
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 5
YAML

# Create a service for the webapp
kubectl create service clusterip webapp -n health-check --tcp=80:80

# Wait for the pod to start and begin restarting due to failed probes
echo "Waiting for pod to start and begin failing health checks..."
sleep 10

# Show the pod status to confirm it's having health check issues
kubectl get pods -n health-check


Review comment (suggestion):
you can use `kubectl --wait` syntax (other evals were originally written with sleep and we moved to kubectl --wait).

Code after:
      containers:
      - name: webapp
        image: nginx:latest
        ports:
        - containerPort: 80
        # The problem: incorrect health probes causing restarts
        livenessProbe:
          httpGet:
            path: /get_status  # Path doesn't exist
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /is_ready  # Path doesn't exist
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 5
YAML

# Create a service for the webapp
kubectl create service clusterip webapp -n health-check --tcp=80:80

# Wait for the pod to start and begin restarting due to failed probes
echo "Waiting for pod to start and begin failing health checks..."
kubectl wait --for=condition=Available=False --timeout=30s deployment/webapp -n health-check || true
Repository: GoogleCloudPlatform/kubectl-ai
File: pkg/ui/blocks.go (Go)

Code before:

	// streaming is true if we are still streaming results in
	streaming bool
}

func NewAgentTextBlock() *AgentTextBlock {
	return &AgentTextBlock{}
}

func (b *AgentTextBlock) attached(doc *Document) {
	b.doc = doc
}

func (b *AgentTextBlock) Document() *Document {
	return b.doc
}

func (b *AgentTextBlock) Text() string {
	return b.text
}

func (b *AgentTextBlock) Streaming() bool {
	return b.streaming
}

func (b *AgentTextBlock) SetStreaming(streaming bool) {
	b.streaming = streaming
	b.doc.blockChanged(b)
}

func (b *AgentTextBlock) SetColor(color ColorValue) {
	b.Color = color
	b.doc.blockChanged(b)
}

func (b *AgentTextBlock) SetText(agentText string) {
	b.text = agentText
	b.doc.blockChanged(b)
}

func (b *AgentTextBlock) AppendText(text string) {
	b.text = b.text + text
	b.doc.blockChanged(b)
}

// FunctionCallRequestBlock is used to render the LLM's request to invoke a function
type FunctionCallRequestBlock struct {
	doc *Document

	// text is populated if this is agent text output
	text string

Review comment (suggestion):
2 cents:

Returning the *AgentTextBlock allows chaining of calls for setting multiple properties which is very convenient in cases where you don't want to have a constructor with long list of ever growing args.

Since it is primarily used for constructing the object, I would recommend renaming the Set to `With`. Or using [self-referential function](https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html) as suggested by Rob pike.

So in caller it will appear:

```Go
 b := ui.NewAgentTextBlock().
            WithStreaming(text).
            WithText(...)
s.doc.AddBlock(b)
```

/cc @justinsb 

Code after:

	// streaming is true if we are still streaming results in
	streaming bool
}

func NewAgentTextBlock() *AgentTextBlock {
	return &AgentTextBlock{}
}

func (b *AgentTextBlock) attached(doc *Document) {
	b.doc = doc
}

func (b *AgentTextBlock) Document() *Document {
	return b.doc
}

func (b *AgentTextBlock) Text() string {
	return b.text
}

func (b *AgentTextBlock) Streaming() bool {
	return b.streaming
}

func (b *AgentTextBlock) SetStreaming(streaming bool) {
	b.streaming = streaming
	b.doc.blockChanged(b)
}

func (b *AgentTextBlock) SetColor(color ColorValue) {
	b.Color = color
	b.doc.blockChanged(b)
}

func (b *AgentTextBlock) SetText(agentText string) {
	b.text = agentText
	b.doc.blockChanged(b)
}

func (b *AgentTextBlock) WithText(agentText string) *AgentTextBlock {
	b.SetText(agentText)
	return b
}

func (b *AgentTextBlock) AppendText(text string) {
	b.text = b.text + text
	b.doc.blockChanged(b)
}

// FunctionCallRequestBlock is used to render the LLM's request to invoke a function
Repository: GoogleCloudPlatform/kubectl-ai
File: pkg/tools/bash_tool.go (Go)

Code before:
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tools

import (
	"bytes"
	"context"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/GoogleCloudPlatform/kubectl-ai/gollm"
	"k8s.io/klog/v2"
)

var bashBin string

func init() {
	// Find the bash executable path using exec.LookPath.
	// On some systems (like NixOS), executables might not be in standard locations like /bin/bash.
	var err error
	bashBin, err = exec.LookPath("bash")
	if err != nil {
		log := klog.FromContext(context.Background())
		log.Info("Warning: 'bash' not found in PATH, defaulting to /bin/bash", "error", err)
		bashBin = "/bin/bash"
	}

	RegisterTool(&BashTool{})
}

// expandShellVar expands shell variables and syntax using bash
func expandShellVar(value string) (string, error) {
	if strings.Contains(value, "~") {
		if len(value) >= 2 && value[0] == '~' && os.IsPathSeparator(value[1]) {
			if runtime.GOOS == "windows" {
				value = filepath.Join(os.Getenv("USERPROFILE"), value[2:])
			} else {
				value = filepath.Join(os.Getenv("HOME"), value[2:])
			}
		}
	}
	return os.ExpandEnv(value), nil
}

type BashTool struct{}


Review comment (refactor):
Can we move this lookup closer to the command execution. Keeping package initialization side-effect free is preferable and also in some cases (simple question/answer) bash is never even invoked.

Also, kubectl execution is also done using bash, so lets do it in a way it works for both.



Code after:
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tools

import (
	"bytes"
	"context"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/GoogleCloudPlatform/kubectl-ai/gollm"
	"k8s.io/klog/v2"
)

func init() {
	RegisterTool(&BashTool{})
}

const (
	defaultBashBin = "/bin/bash"
)

// Find the bash executable path using exec.LookPath.
// On some systems (like NixOS), executables might not be in standard locations like /bin/bash.
func lookupBashBin() string {
	actualBashPath, err := exec.LookPath("bash")
	if err != nil {
		klog.Warningf("'bash' not found in PATH, defaulting to %s: %v", defaultBashBin, err)
		return defaultBashBin
	}
	return actualBashPath
}

// expandShellVar expands shell variables and syntax using bash
func expandShellVar(value string) (string, error) {
	if strings.Contains(value, "~") {
		if len(value) >= 2 && value[0] == '~' && os.IsPathSeparator(value[1]) {
			if runtime.GOOS == "windows" {
				value = filepath.Join(os.Getenv("USERPROFILE"), value[2:])
			} else {
				value = filepath.Join(os.Getenv("HOME"), value[2:])
			}
		}
	}
	return os.ExpandEnv(value), nil
}
Repository: GoogleCloudPlatform/kubectl-ai
File: cmd/main.go (Go)

Code before:
		*u = UserInterface(s)
		return nil
	default:
		return fmt.Errorf("invalid user interface: %s", s)
	}
}

func (u *UserInterface) String() string {
	return string(*u)
}

func (u *UserInterface) Type() string {
	return "UserInterface"
}

func (o *Options) InitDefaults() {
	o.ProviderID = "gemini"
	o.ModelID = "gemini-2.5-pro-preview-03-25"
	// by default, confirm before executing kubectl commands that modify resources in the cluster.
	o.SkipPermissions = false
	o.MCPServer = false
	// We now default to our strongest model (gemini-2.5-pro-exp-03-25) which supports tool use natively.
	// so we don't need shim.
	o.EnableToolUseShim = false
	o.Quiet = false
	o.MCPServer = false
	o.MaxIterations = 20
	o.KubeConfigPath = ""
	o.PromptTemplateFilePath = ""
	o.ExtraPromptPaths = []string{}
	o.TracePath = filepath.Join(os.TempDir(), "kubectl-ai-trace.txt")
	o.RemoveWorkDir = false
	o.ToolConfigPath = []string{
		filepath.Join("{CONFIG}", "kubectl-ai", "tools.yaml"),
		filepath.Join("{HOME}", ".config", "kubectl-ai", "tools.yaml"),
	}

	// Default to terminal UI
	o.UserInterface = UserInterfaceTerminal

	// Default to not skipping SSL verification
	o.SkipVerifySSL = false
}

func (o *Options) LoadConfiguration(b []byte) error {
	if err := yaml.Unmarshal(b, &o); err != nil {
		return fmt.Errorf("parsing configuration: %w", err)
	}
	return nil
}


Review comment (refactor):
I think we can simplify this by having a single variable named `defaultLookupPaths` and then just appending the config or tools file name.

Code after:
	switch s {
	case "terminal", "html":
		*u = UserInterface(s)
		return nil
	default:
		return fmt.Errorf("invalid user interface: %s", s)
	}
}

func (u *UserInterface) String() string {
	return string(*u)
}

func (u *UserInterface) Type() string {
	return "UserInterface"
}

var defaultToolConfigPaths = []string{
	filepath.Join("{CONFIG}", "kubectl-ai", "tools.yaml"),
	filepath.Join("{HOME}", ".config", "kubectl-ai", "tools.yaml"),
}

var defaultConfigPaths = []string{
	filepath.Join("{CONFIG}", "kubectl-ai", "config.yaml"),
	filepath.Join("{HOME}", ".config", "kubectl-ai", "config.yaml"),
}

func (o *Options) InitDefaults() {
	o.ProviderID = "gemini"
	o.ModelID = "gemini-2.5-pro-preview-03-25"
	// by default, confirm before executing kubectl commands that modify resources in the cluster.
	o.SkipPermissions = false
	o.MCPServer = false
	// We now default to our strongest model (gemini-2.5-pro-exp-03-25) which supports tool use natively.
	// so we don't need shim.
	o.EnableToolUseShim = false
	o.Quiet = false
	o.MCPServer = false
	o.MaxIterations = 20
	o.KubeConfigPath = ""
	o.PromptTemplateFilePath = ""
	o.ExtraPromptPaths = []string{}
	o.TracePath = filepath.Join(os.TempDir(), "kubectl-ai-trace.txt")
	o.RemoveWorkDir = false
	o.ToolConfigPaths = defaultToolConfigPaths
	// Default to terminal UI
	o.UserInterface = UserInterfaceTerminal

	// Default to not skipping SSL verification
	o.SkipVerifySSL = false
}
Repository: GoogleCloudPlatform/kubectl-ai
File: k8s-bench/eval.go (Go)

Code before:
		return result
	}
	taskDir = taskDirAbs
	x.taskDir = taskDir

	defer func() {
		if err := x.runCleanup(ctx); err != nil {
			fmt.Printf("Warning: cleanup failed for task %s: %v\n", taskID, err)
		}
	}()

	if err := x.runSetup(ctx); err != nil {
		// Unexpected error
		result.Error = err.Error()
		return result
	}

	// Run the agent
	agentOutput, err := x.runAgent(ctx)
	if err != nil {
		// Unexpected error
		result.Error = err.Error()
		return result
	}

	expectationsMet := false
	var expectationFailures []model.Failure
	if len(task.Expect) > 0 {
		expectationsMet = true

		// find the output after the last run command and search it
		var lastCmdOutput string
		lastToolRunIndex := strings.LastIndex(agentOutput, "Running:")
		if lastToolRunIndex == -1 {
			// if no tool run found, parse the entire output
			lastCmdOutput = agentOutput
		} else {
			remaining := agentOutput[lastToolRunIndex:]
			newlineIndex := strings.Index(remaining, "\n")
			if newlineIndex != -1 {
				lastCmdOutput = remaining[newlineIndex+1:]
			}
			// if no newline, lastCmdOutput is empty string
		}

		for _, expect := range task.Expect {
			if expect.Contains != "" {
				re, err := regexp.Compile(expect.Contains)
				if err != nil {
					expectationFailures = append(expectationFailures, model.Failure{
						Message: fmt.Sprintf("invalid regex %q in task spec: %v", expect.Contains, err),

Review comment (bug):
We could remove `expectationsMet` and check `len(expectationsFailures) == 0 && len(task.Expect) > 0` at 296 instead to simplify.

Code after:
		return result
	}
	taskDir = taskDirAbs
	x.taskDir = taskDir

	defer func() {
		if err := x.runCleanup(ctx); err != nil {
			fmt.Printf("Warning: cleanup failed for task %s: %v\n", taskID, err)
		}
	}()

	if err := x.runSetup(ctx); err != nil {
		// Unexpected error
		result.Error = err.Error()
		return result
	}

	// Run the agent
	agentOutput, err := x.runAgent(ctx)
	if err != nil {
		// Unexpected error
		result.Error = err.Error()
		return result
	}

	var expectationFailures []model.Failure

	if len(task.Expect) > 0 {
		// find the output after the last run command and search it
		var lastCmdOutput string
		lastToolRunIndex := strings.LastIndex(agentOutput, "Running:")
		if lastToolRunIndex == -1 {
			// if no tool run found, parse the entire output
			lastCmdOutput = agentOutput
		} else {
			remaining := agentOutput[lastToolRunIndex:]
			newlineIndex := strings.Index(remaining, "\n")
			if newlineIndex != -1 {
				lastCmdOutput = remaining[newlineIndex+1:]
			}
			// if no newline, lastCmdOutput is empty string
		}

		for _, expect := range task.Expect {
			if expect.Contains != "" {
				re, err := regexp.Compile(expect.Contains)
				if err != nil {
					expectationFailures = append(expectationFailures, model.Failure{
						Message: fmt.Sprintf("invalid regex %q in task spec: %v", expect.Contains, err),
					})
					continue
Repository: GoogleCloudPlatform/kubectl-ai
File: k8s-bench/tasks/scale-down-deployment/setup.sh (Shell)

Code before:
#!/usr/bin/env bash
# Create namespace and a deployment with initial replicas
kubectl delete namespace scale-down-test --ignore-not-found
kubectl create namespace scale-down-test
kubectl create deployment web-service --image=nginx --replicas=2 -n scale-down-test
# Wait for initial deployment to be ready
for i in {1..30}; do
    if kubectl --request-timeout=10s get deployment web-service -n scale-down-test -o jsonpath='{.status.availableReplicas}' | grep -q "4"; then
        exit 0
    fi
    sleep 1
done 

Review comment (suggestion):
If we're making only 2 replicas shouldn't we also grep for 2 here? Also just thinking, adding the timeout of 10s might be enough of a change, since it would cut the time it waits here to 1/3rd (30s default, I believe).

Code after:
#!/usr/bin/env bash
# Create namespace and a deployment with initial replicas
kubectl delete namespace scale-down-test --ignore-not-found
kubectl create namespace scale-down-test
kubectl create deployment web-service --image=nginx --replicas=2 -n scale-down-test
# Wait for initial deployment to be ready
for i in {1..30}; do
    if kubectl --request-timeout=10s get deployment web-service -n scale-down-test -o jsonpath='{.status.availableReplicas}' | grep -q "2"; then
        exit 0
    fi
    sleep 1
done
Repository: GoogleContainerTools/jib
File: jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/ProxyProvider.java (Java)

Code before:
   */
  private static void propagateProxyProperties(Proxy proxy) {
    if (proxy.getProtocol().equalsIgnoreCase("http")) {
      propagateProxyProperty("http.proxyHost", proxy.getHost());
      propagateProxyProperty("http.proxyPort", String.valueOf(proxy.getPort()));
      propagateProxyProperty("http.proxyUser", proxy.getUsername());
      propagateProxyProperty("http.proxyPassword", proxy.getPassword());
    } else if (proxy.getProtocol().equalsIgnoreCase("https")) {
      propagateProxyProperty("https.proxyHost", proxy.getHost());
      propagateProxyProperty("https.proxyPort", String.valueOf(proxy.getPort()));
      propagateProxyProperty("https.proxyUser", proxy.getUsername());
      propagateProxyProperty("https.proxyPassword", proxy.getPassword());
    }
    propagateProxyProperty("http.nonProxyHosts", proxy.getNonProxyHosts());
  }

  /**
   * Propagate Maven proxy property into system property to be picked up by Connections. Only set
   * the system property if not already set.
   *
   * @param name proxy system property name
   * @param value proxy system property value
   */
  private static void propagateProxyProperty(String name, String value) {
    if (value != null && System.getProperty(name) == null) {
      System.setProperty(name, value);
    }
  }
}


Review comment (suggestion):
This will only work for a single proxy.  Although this is admittedly better than our current level of support :-)  But I suspect the approach we'll really need here is to populate a ProxySelector — since the proxies can be per-host — and configure our `Connection` / `HttpTransport` instance to use that selector.


Code after:
   * com.google.cloud.tools.jib.http.Connection}, while connecting to container image registries.
   *
   * @param proxy Maven proxy
   */
  private static void setProxyProperties(Proxy proxy) {
    String protocol = proxy.getProtocol();
    if (protocol != null && !proxyPropertiesSet(protocol)) {
      setProxyProperty(protocol + ".proxyHost", proxy.getHost());
      setProxyProperty(protocol + ".proxyPort", String.valueOf(proxy.getPort()));
      setProxyProperty(protocol + ".proxyUser", proxy.getUsername());
      setProxyProperty(protocol + ".proxyPassword", proxy.getPassword());
      setProxyProperty("http.nonProxyHosts", proxy.getNonProxyHosts());
    }
  }

  /**
   * Set proxy system property if it has a proper value
   *
   * @param property property name
   * @param value property value
   */
  private static void setProxyProperty(String property, String value) {
    if (property != null && value != null) {
      System.setProperty(property, value);
    }
  }

  /**
   * Check if any proxy system properties are already set for a given protocol. Note, <code>
   * nonProxyHosts</code> is excluded as it can only be set with <code>http</code>.
   *
   * @param protocol protocol
   */
  private static boolean proxyPropertiesSet(String protocol) {
    return PROXY_PROPERTIES.stream().anyMatch(p -> System.getProperty(protocol + "." + p) != null);
  }
}
Repository: GoogleContainerTools/jib
File: jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/ProxyProvider.java (Java)

Code before:
   *
   * @param proxy Maven proxy
   */
  private static void propagateProxyProperties(Proxy proxy) {
    if (proxy.getProtocol().equalsIgnoreCase("http")) {
      propagateProxyProperty("http.proxyHost", proxy.getHost());
      propagateProxyProperty("http.proxyPort", String.valueOf(proxy.getPort()));
      propagateProxyProperty("http.proxyUser", proxy.getUsername());
      propagateProxyProperty("http.proxyPassword", proxy.getPassword());
    } else if (proxy.getProtocol().equalsIgnoreCase("https")) {
      propagateProxyProperty("https.proxyHost", proxy.getHost());
      propagateProxyProperty("https.proxyPort", String.valueOf(proxy.getPort()));
      propagateProxyProperty("https.proxyUser", proxy.getUsername());
      propagateProxyProperty("https.proxyPassword", proxy.getPassword());
    }
    propagateProxyProperty("http.nonProxyHosts", proxy.getNonProxyHosts());
  }

  /**
   * Propagate Maven proxy property into system property to be picked up by Connections. Only set
   * the system property if not already set.
   *
   * @param name proxy system property name
   * @param value proxy system property value
   */
  private static void propagateProxyProperty(String name, String value) {
    if (value != null && System.getProperty(name) == null) {
      System.setProperty(name, value);
    }
  }
}


Review comment (suggestion):
Use `@Nullable` for `value`: `...(String name, @Nullable String value) {`

Code after:
   * Set proxy system properties based on Maven proxy configuration. These system properties will be
   * picked up by {@link java.net.ProxySelector} used in {@link
   * com.google.cloud.tools.jib.http.Connection}, while connecting to container image registries.
   *
   * @param proxy Maven proxy
   */
  private static void setProxyProperties(Proxy proxy) {
    String protocol = proxy.getProtocol();
    if (protocol != null && !proxyPropertiesSet(protocol)) {
      setProxyProperty(protocol + ".proxyHost", proxy.getHost());
      setProxyProperty(protocol + ".proxyPort", String.valueOf(proxy.getPort()));
      setProxyProperty(protocol + ".proxyUser", proxy.getUsername());
      setProxyProperty(protocol + ".proxyPassword", proxy.getPassword());
      setProxyProperty("http.nonProxyHosts", proxy.getNonProxyHosts());
    }
  }

  /**
   * Set proxy system property if it has a proper value
   *
   * @param property property name
   * @param value property value
   */
  private static void setProxyProperty(String property, String value) {
    if (property != null && value != null) {
      System.setProperty(property, value);
    }
  }

  /**
   * Check if any proxy system properties are already set for a given protocol. Note, <code>
   * nonProxyHosts</code> is excluded as it can only be set with <code>http</code>.
   *
   * @param protocol protocol
   */
  private static boolean proxyPropertiesSet(String protocol) {
    return PROXY_PROPERTIES.stream().anyMatch(p -> System.getProperty(protocol + "." + p) != null);
  }
}