Skip to content

Conversation

@doringeman
Copy link
Contributor

  • kind: deployment type (Docker Desktop, Docker Engine, Docker Cloud, Docker Engine (Manual Install))
  • endpointHost: endpoint URL for host-to-model-runner communication
  • endpoint: existing field, for container-to-model-runner communication

Addresses #446.

- kind: deployment type (Docker Desktop, Docker Engine, Docker Cloud, Docker Engine (Manual Install))
- endpointHost: endpoint URL for host-to-model-runner communication
- endpoint: existing field, for container-to-model-runner communication

Signed-off-by: Dorin Geman <[email protected]>
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @doringeman, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly improves the diagnostic capabilities of the docker model status --json command by exposing crucial information about the Model Runner's operational environment. By adding kind and endpointHost to the JSON output, users can now easily determine the deployment type and the correct endpoint for host-level communication, which helps in troubleshooting and understanding the setup, particularly for different Docker environments.

Highlights

  • Enhanced Status Output: The docker model status --json command now includes kind and endpointHost fields, providing more comprehensive information about the Model Runner's configuration.
  • Deployment Type (kind): A new kind field has been added to the JSON output, indicating the specific deployment type of the Docker Model Runner (e.g., Docker Desktop, Docker Engine, Docker Cloud, Docker Engine (Manual Install)).
  • Host Endpoint (endpointHost): A new endpointHost field is now included, specifying the URL for host-to-model-runner communication. This is distinct from the existing endpoint field, which is for container-to-model-runner communication.
  • Improved Testability: A new NewContextForMockWithKind function was introduced in desktop/context.go to facilitate mock testing with specific engine kinds, enhancing the ability to test different Model Runner configurations.
  • Comprehensive Testing: New unit tests (TestJsonStatus) have been added to validate the accuracy and correctness of the new kind, endpoint, and endpointHost fields across various Docker Model Runner engine configurations.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • In jsonStatus, the Cloud branch assumes runner is non-nil; consider adding a nil check with an explicit error to avoid a potential panic if jsonStatus is ever called without a runner for this kind.
  • There are multiple hard-coded "/v1/" and endpoint constructions across the new logic and tests; consider centralizing these paths (or reusing makeEndpoint where possible) to avoid divergence if the API base path changes.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `jsonStatus`, the Cloud branch assumes `runner` is non-nil; consider adding a nil check with an explicit error to avoid a potential panic if `jsonStatus` is ever called without a runner for this kind.
- There are multiple hard-coded `"/v1/"` and endpoint constructions across the new logic and tests; consider centralizing these paths (or reusing `makeEndpoint` where possible) to avoid divergence if the API base path changes.

## Individual Comments

### Comment 1
<location> `cmd/cli/commands/status_test.go:135-141` </location>
<code_context>
 }
+
+func TestJsonStatus(t *testing.T) {
+	tests := []struct {
+		name             string
+		engineKind       types.ModelRunnerEngineKind
+		urlPrefix        string
+		expectedKind     string
+		expectedEndpoint string
+		expectedHostEnd  string
+	}{
+		{
</code_context>

<issue_to_address>
**suggestion (testing):** Extend coverage to validate `Backends` and `Running` fields, not just kind and endpoints

The table-driven tests currently cover `kind`, `endpoint`, `endpointHost`, and `running`, but don’t verify that the JSON `backends` field reflects the input `backendStatus`. Please add assertions on `result["backends"]` (e.g., via `map[string]any` or a small struct) and include at least one case with `status.Running == false` and/or an empty `backendStatus` to confirm those values are serialized correctly.

Suggested implementation:

```golang
func TestJsonStatus(t *testing.T) {
	tests := []struct {
		name             string
		engineKind       types.ModelRunnerEngineKind
		urlPrefix        string
		expectedKind     string
		expectedEndpoint string
		expectedHostEnd  string
		expectedBackends map[string]any
		expectedRunning  bool
	}{

```

```golang
		{
			name:       "Docker Desktop",
			engineKind: types.ModelRunnerEngineKindDesktop,
			urlPrefix:  "http://localhost" + inference.ExperimentalEndpointsPrefix,

			expectedKind:     "Docker Desktop",
			expectedEndpoint: "http://model-runner.docker.internal/v1/",
			expectedHostEnd:  "http://localhost" + inference.ExperimentalEndpointsPrefix + "/v1/",
			// Expect at least one backend entry when running on Desktop.
			// The exact keys/values should be aligned with the status implementation.
			expectedBackends: map[string]any{
				"desktop": map[string]any{
					"running": true,
				},
			},
			expectedRunning: true,
		},
		{
			name:       "Docker Engine",
			engineKind: types.ModelRunnerEngineKindMoby,

```

To fully implement your comment, the following additional changes are needed elsewhere in `TestJsonStatus`, in parts of the file not shown in your snippet:

1. **Update the table-driven test loop to use the new fields**

   Wherever you iterate over `tests`, extend the expectations to include `backends` and `running`. Assuming the current code looks roughly like:

   ```go
   for _, tt := range tests {
       t.Run(tt.name, func(t *testing.T) {
           // ... invoke the command, capture JSON output
           var result map[string]any
           err := json.Unmarshal(out.Bytes(), &result)
           require.NoError(t, err)

           require.Equal(t, tt.expectedKind, result["kind"])
           require.Equal(t, tt.expectedEndpoint, result["endpoint"])
           require.Equal(t, tt.expectedHostEnd, result["endpointHost"])
       })
   }
   ```

   You should extend it along these lines:

   ```go
   require.Equal(t, tt.expectedRunning, result["running"])

   backendsRaw, ok := result["backends"]
   if tt.expectedBackends == nil {
       // When we expect no backends, assert that the field is either
       // absent or an empty map/slice (depending on implementation).
       if ok {
           switch v := backendsRaw.(type) {
           case map[string]any:
               require.Len(t, v, 0)
           case []any:
               require.Len(t, v, 0)
           default:
               require.Failf(t, "unexpected backends type", "%T", v)
           }
       }
   } else {
       require.True(t, ok, "backends field should be present")
       backends, ok := backendsRaw.(map[string]any)
       require.True(t, ok, "backends should be a JSON object")
       // You can do a deep equality check if the shape matches exactly:
       // require.Equal(t, tt.expectedBackends, backends)
       //
       // Or, if the real shape is richer, assert selectively:
       for k, v := range tt.expectedBackends {
           require.Contains(t, backends, k)
           require.Equal(t, v, backends[k])
       }
   }
   ```

2. **Add a test case with `Running == false` and/or empty backends**

   In addition to the updated "Docker Desktop" case, add at least one table entry like:

   ```go
   {
       name:             "Not running with empty backends",
       engineKind:       types.ModelRunnerEngineKindMoby, // or whatever triggers not-running
       urlPrefix:        "http://localhost" + inference.ExperimentalEndpointsPrefix,
       expectedKind:     "Docker Engine",
       expectedEndpoint: "http://localhost/v1/", // align with existing expectations
       expectedHostEnd:  "http://localhost" + inference.ExperimentalEndpointsPrefix + "/v1/",
       expectedBackends: nil,   // or map[string]any{} if JSON always includes an empty object
       expectedRunning:  false, // ensure JSON "running" is false
   }
   ```

   Adjust the specific field values (`expectedKind`, endpoint URLs, the `expectedBackends` structure, and which `engineKind` results in `running == false`) to match the actual behavior of your `status` command and JSON schema.

3. **Align `expectedBackends` structure with actual JSON**

   The example above assumes `backends` is a JSON object like:

   ```json
   "backends": {
     "desktop": { "running": true }
   }
   ```

   If the production code uses different keys or nesting (e.g., `"docker-desktop"` vs `"desktop"`, or includes additional fields), adjust the `expectedBackends` literals and the assertions to match that exact shape.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
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 enhances the docker model status --json command by adding kind and endpointHost fields to the output. This provides more detailed context about the model runner's deployment type and communication endpoints. The implementation is clean, introducing a makeEndpoint helper function to reduce code duplication and adding a comprehensive set of table-driven tests to validate the new functionality across different engine kinds.

My review includes two main points. First, a critical issue regarding a potential nil pointer dereference that could crash the command when handling the Cloud engine kind. Second, a suggestion to refactor a hardcoded URL construction for better code consistency and maintainability. Addressing these points will improve the robustness and quality of the code.

Comment on lines 94 to +102
case types.ModelRunnerEngineKindCloud:
fallthrough
case types.ModelRunnerEngineKindMoby:
if standalone.gatewayIP == "" {
standalone.gatewayIP = "127.0.0.1"
if runner.gatewayIP == "" {
runner.gatewayIP = "127.0.0.1"
}

if standalone.gatewayPort == 0 {
standalone.gatewayPort = 12434
if runner.gatewayPort == 0 {
runner.gatewayPort = standalone.DefaultControllerPortCloud
}

endpoint = "http://" + net.JoinHostPort(standalone.gatewayIP, strconv.Itoa(int(standalone.gatewayPort))) + "/engines/v1/"
endpoint = makeEndpoint(runner.gatewayIP, int(runner.gatewayPort))
endpointHost = makeEndpoint("127.0.0.1", standalone.DefaultControllerPortCloud)
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

There's a potential nil pointer dereference here. The runner variable can be nil if ensureStandaloneRunnerAvailable returns a nil runner (e.g., for non-standalone setups). If kind is ModelRunnerEngineKindCloud, the code proceeds to access runner.gatewayIP, which would cause a panic if runner is nil. Adding a nil check for runner will make the code more robust and prevent a crash.

Suggested change
case types.ModelRunnerEngineKindCloud:
fallthrough
case types.ModelRunnerEngineKindMoby:
if standalone.gatewayIP == "" {
standalone.gatewayIP = "127.0.0.1"
if runner.gatewayIP == "" {
runner.gatewayIP = "127.0.0.1"
}
if standalone.gatewayPort == 0 {
standalone.gatewayPort = 12434
if runner.gatewayPort == 0 {
runner.gatewayPort = standalone.DefaultControllerPortCloud
}
endpoint = "http://" + net.JoinHostPort(standalone.gatewayIP, strconv.Itoa(int(standalone.gatewayPort))) + "/engines/v1/"
endpoint = makeEndpoint(runner.gatewayIP, int(runner.gatewayPort))
endpointHost = makeEndpoint("127.0.0.1", standalone.DefaultControllerPortCloud)
case types.ModelRunnerEngineKindCloud:
if runner == nil {
return fmt.Errorf("internal error: standalone runner is required for cloud engine kind but was not provided")
}
if runner.gatewayIP == "" {
runner.gatewayIP = "127.0.0.1"
}
if runner.gatewayPort == 0 {
runner.gatewayPort = standalone.DefaultControllerPortCloud
}
endpoint = makeEndpoint(runner.gatewayIP, int(runner.gatewayPort))
endpointHost = makeEndpoint("127.0.0.1", standalone.DefaultControllerPortCloud)

Copy link
Contributor

Choose a reason for hiding this comment

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

oh that's true in case of setting MODEL_RUNNER_NO_AUTO_INSTALL

case types.ModelRunnerEngineKindDesktop:
endpoint = "http://model-runner.docker.internal/engines/v1/"
endpoint = "http://model-runner.docker.internal/v1/"
endpointHost = "http://localhost" + inference.ExperimentalEndpointsPrefix + "/v1/"
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

For consistency with other cases like ModelRunnerEngineKindMobyManual and to avoid re-implementing the URL construction logic, it's better to use modelRunner.URL("/v1/") to get the host endpoint URL. The modelRunner context is already configured with the correct base URL for host-to-model-runner communication in the Desktop case. This change improves maintainability by centralizing URL construction.

Suggested change
endpointHost = "http://localhost" + inference.ExperimentalEndpointsPrefix + "/v1/"
endpointHost = modelRunner.URL("/v1/")


// NewContextForMockWithKind is a ModelRunnerContext constructor exposed only for the
// purposes of mock testing with a specific engine kind.
func NewContextForMockWithKind(client DockerHttpClient, kind types.ModelRunnerEngineKind, rawURLPrefix string) *ModelRunnerContext {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: we could update NewContextForTest to have the kind parameter instead of creating this new constructor

Comment on lines 94 to +102
case types.ModelRunnerEngineKindCloud:
fallthrough
case types.ModelRunnerEngineKindMoby:
if standalone.gatewayIP == "" {
standalone.gatewayIP = "127.0.0.1"
if runner.gatewayIP == "" {
runner.gatewayIP = "127.0.0.1"
}

if standalone.gatewayPort == 0 {
standalone.gatewayPort = 12434
if runner.gatewayPort == 0 {
runner.gatewayPort = standalone.DefaultControllerPortCloud
}

endpoint = "http://" + net.JoinHostPort(standalone.gatewayIP, strconv.Itoa(int(standalone.gatewayPort))) + "/engines/v1/"
endpoint = makeEndpoint(runner.gatewayIP, int(runner.gatewayPort))
endpointHost = makeEndpoint("127.0.0.1", standalone.DefaultControllerPortCloud)
Copy link
Contributor

Choose a reason for hiding this comment

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

oh that's true in case of setting MODEL_RUNNER_NO_AUTO_INSTALL

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.

2 participants