diff --git a/garden-cli/src/commands/call.ts b/garden-cli/src/commands/call.ts index 376691782c..c21746a84a 100644 --- a/garden-cli/src/commands/call.ts +++ b/garden-cli/src/commands/call.ts @@ -19,8 +19,9 @@ import { import { splitFirst } from "../util/util" import { ParameterError, RuntimeError } from "../exceptions" import { EntryStyle } from "../logger/types" -import { pick } from "lodash" +import { pick, find } from "lodash" import { PluginContext } from "../plugin-context" +import { ServiceEndpoint } from "../types/service" import dedent = require("dedent") export const callArgs = { @@ -52,7 +53,6 @@ export class CallCommand extends Command { async action(ctx: PluginContext, args: Args): Promise { let [serviceName, path] = splitFirst(args.serviceAndPath, "/") - path = "/" + path // TODO: better error when service doesn't exist const service = await ctx.getService(serviceName) @@ -73,24 +73,41 @@ export class CallCommand extends Command { } // find the correct endpoint to call - let matchedEndpoint + let matchedEndpoint: ServiceEndpoint | null = null let matchedPath - for (const endpoint of status.endpoints) { - // we can't easily support raw TCP or UDP in a command like this - if (endpoint.protocol !== "http" && endpoint.protocol !== "https") { - continue + // we can't easily support raw TCP or UDP in a command like this + const endpoints = status.endpoints.filter(e => e.protocol === "http" || e.protocol === "https") + + if (!path) { + // if no path is specified and there's a root endpoint (path === "/") we use that + const rootEndpoint = find(endpoints, e => e.paths && e.paths.includes("/")) + + if (rootEndpoint) { + matchedEndpoint = rootEndpoint + matchedPath = "/" + } else { + // if there's no root endpoint, pick the first endpoint + matchedEndpoint = endpoints[0] + matchedPath = endpoints[0].paths ? endpoints[0].paths![0] : "" } - if (endpoint.paths) { - for (const endpointPath of endpoint.paths) { - if (path.startsWith(endpointPath) && (!matchedPath || endpointPath.length > matchedPath.length)) { - matchedPath = endpointPath - matchedEndpoint = endpoint + path = matchedPath + + } else { + path = "/" + path + + for (const endpoint of status.endpoints) { + if (endpoint.paths) { + for (const endpointPath of endpoint.paths) { + if (path.startsWith(endpointPath) && (!matchedPath || endpointPath.length > matchedPath.length)) { + matchedPath = endpointPath + matchedEndpoint = endpoint + } } + } else if (!matchedPath) { + matchedEndpoint = endpoint } - } else if (!matchedPath) { - matchedEndpoint = endpoint } } @@ -102,7 +119,7 @@ export class CallCommand extends Command { }) } - const url = resolve(matchedEndpoint.url, path) + const url = resolve(matchedEndpoint.url, path || matchedPath) // TODO: support POST requests with request body const method = "get" diff --git a/garden-cli/test/data/test-project-b/module-c/garden.yml b/garden-cli/test/data/test-project-b/module-c/garden.yml index d7a6f7bf6d..9214aa540d 100644 --- a/garden-cli/test/data/test-project-b/module-c/garden.yml +++ b/garden-cli/test/data/test-project-b/module-c/garden.yml @@ -10,6 +10,13 @@ module: ports: - name: http containerPort: 8080 + - name: service-d + endpoints: + - paths: [/path-d] + port: http + ports: + - name: http + containerPort: 8080 build: dependencies: - module-b diff --git a/garden-cli/test/src/commands/call.ts b/garden-cli/test/src/commands/call.ts index 361c2f9bb3..085408dd01 100644 --- a/garden-cli/test/src/commands/call.ts +++ b/garden-cli/test/src/commands/call.ts @@ -19,6 +19,15 @@ const testProvider: PluginFactory = () => { url: "http://service-a.test-project-b.local.app.garden:32000", }], }, + "service-b": { + state: "ready", + endpoints: [{ + protocol: "http", + hostname: "service-b.test-project-b.local.app.garden", + paths: ["/"], + url: "http://service-b.test-project-b.local.app.garden:32000", + }], + }, "service-c": { state: "ready", }, @@ -73,6 +82,52 @@ describe("commands.call", () => { }) + it("should default to the path '/' if that is exposed if no path is requested", async () => { + const garden = await Garden.factory(projectRootB, { plugins: [testProvider] }) + const ctx = garden.pluginContext + const command = new CallCommand() + + nock("http://service-a.test-project-b.local.app.garden:32000") + .get("/path-a") + .reply(200, "bla") + + const { result } = await command.action( + ctx, + { + serviceAndPath: "service-a", + }, + ) + + expect(result.url).to.equal("http://service-a.test-project-b.local.app.garden:32000/path-a") + expect(result.serviceName).to.equal("service-a") + expect(result.path).to.equal("/path-a") + expect(result.response.status).to.equal(200) + expect(result.response.data).to.equal("bla") + }) + + it("should otherwise use the first defined endpoint if no path is requested", async () => { + const garden = await Garden.factory(projectRootB, { plugins: [testProvider] }) + const ctx = garden.pluginContext + const command = new CallCommand() + + nock("http://service-b.test-project-b.local.app.garden:32000") + .get("/") + .reply(200, "bla") + + const { result } = await command.action( + ctx, + { + serviceAndPath: "service-b", + }, + ) + + expect(result.url).to.equal("http://service-b.test-project-b.local.app.garden:32000/") + expect(result.serviceName).to.equal("service-b") + expect(result.path).to.equal("/") + expect(result.response.status).to.equal(200) + expect(result.response.data).to.equal("bla") + }) + it("should error if service isn't running", async () => { const garden = await Garden.factory(projectRootB, { plugins: [testProvider] }) const ctx = garden.pluginContext @@ -82,7 +137,7 @@ describe("commands.call", () => { await command.action( ctx, { - serviceAndPath: "service-b/path-b", + serviceAndPath: "service-d/path-d", }, ) } catch (err) { diff --git a/garden-cli/test/src/commands/deploy.ts b/garden-cli/test/src/commands/deploy.ts index 522596905a..c89fbb2ab7 100644 --- a/garden-cli/test/src/commands/deploy.ts +++ b/garden-cli/test/src/commands/deploy.ts @@ -88,6 +88,7 @@ describe("DeployCommand", () => { "deploy.service-a": { version: "1", state: "ready" }, "deploy.service-b": { version: "1", state: "ready" }, "deploy.service-c": { version: "1", state: "ready" }, + "deploy.service-d": { version: "1", state: "ready" }, }) })