Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 38 additions & 25 deletions packages/k8s-client/src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
* SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors
* SPDX-License-Identifier: Apache-2.0
*/
import https from "https"
import { buildUrl } from "./urlHelpers"
import * as logger from "./logger"
import { K8sApiError } from "./apiErrorHandler"
import https from "https"

// Define the shape of the options parameter
interface RequestOptions {
Expand All @@ -16,8 +16,8 @@
mode?: RequestMode
cache?: RequestCache
credentials?: RequestCredentials
ignoreSsl?: boolean // New option to ignore SSL certificate validation
debug?: boolean // Optional debug flag
ignoreSsl?: boolean
debug?: boolean
[key: string]: any
}

Expand All @@ -27,11 +27,31 @@
return response
} else {
const error = new Error(response.statusText || `${response.status}`)
;(error as K8sApiError).response = response // Type assertion to attach the response to the error
;(error as K8sApiError).response = response
throw error
}
}

// Helper function to create HTTPS agent only in Node.js
function createHttpsAgent(ignoreSsl: boolean, url: string): https.Agent | undefined {
if (!ignoreSsl || !url.startsWith("https:") || typeof window !== "undefined") {
return undefined
}

try {
if (!https?.Agent) {
return undefined
}

return new https.Agent({
rejectUnauthorized: false,
Comment thread
andypf marked this conversation as resolved.
Dismissed
})
} catch (_error) {
Comment thread
andypf marked this conversation as resolved.
// https module not available (browser environment or bundle)
return undefined
}
}

/**
* Creates a request.
*
Expand All @@ -41,57 +61,50 @@
* @return {Promise<Response>} The response promise.
*/
function request(method: string, url: string, options: RequestOptions = {}): Promise<Response> {
// add params to url
// Add params to url
if (options.params) url = buildUrl(url, options.params)

// Handle SSL ignore option
const { ignoreSsl, ...restOptions } = options
const { ignoreSsl, debug, ...restOptions } = options

// Create HTTPS agent if SSL should be ignored for HTTPS URLs
let agent: https.Agent | undefined
// Create HTTPS agent if needed (Node.js only)
Comment thread
andypf marked this conversation as resolved.
const agent = createHttpsAgent(ignoreSsl || false, url)

if (ignoreSsl && url.startsWith("https:") && typeof window === "undefined") {
agent = new https.Agent({
rejectUnauthorized: false,
})

// Log warning when SSL verification is disabled
if (process.env.NODE_ENV !== "test" && options.debug === true) {
// Avoid spam in tests
logger.debug(`⚠️ SSL verification disabled for request to: ${url}`)
}
// Log warning when SSL verification is disabled
if (agent && debug === true && process.env.NODE_ENV !== "test") {
logger.debug(`⚠️ SSL verification disabled for request to: ${url}`)
}

// add allowed options to fetch (excluding ignoreSsl as it's handled separately)
// Add allowed options to fetch (excluding ignoreSsl as it's handled separately)
const requestFields = ["signal", "headers", "body", "mode", "cache", "credentials"] as const
const fetchOptions: RequestInit & { agent?: https.Agent } = requestFields.reduce(
const fetchOptions: RequestInit & { agent?: typeof agent } = requestFields.reduce(
(map, key) => {
if (restOptions[key]) {
return { ...map, [key]: restOptions[key] }
}
return map
},
{ credentials: "same-origin", method } as RequestInit & { agent?: https.Agent }
{ credentials: "same-origin", method } as RequestInit & { agent?: typeof agent }
)

// Add agent if SSL should be ignored (Node.js environment only)
// Add agent if available
if (agent) {
fetchOptions.agent = agent
}

// stringify body if it's an object
// Stringify body if it's an object
if (fetchOptions.body && typeof fetchOptions.body !== "string") {
fetchOptions.body = JSON.stringify(fetchOptions.body)
}

if (options.debug === true) {
if (debug === true) {
logger.debug("fetch >", url, {
...fetchOptions,
agent: agent ? "HTTPS Agent (SSL ignored)" : fetchOptions.agent,
})
}

// make the call
// Make the call
return fetch(url, fetchOptions).then(checkStatus)
}

Expand Down
11 changes: 1 addition & 10 deletions packages/k8s-client/test/request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@ import https from "https"

// Mock https module with proper return value
vi.mock("https", () => ({
default: {
Agent: vi.fn(),
},
Agent: vi.fn(),
default: { Agent: vi.fn() },
}))

const testUrl = "https://apiEndpoint.com"
Expand All @@ -26,13 +24,6 @@ describe("request", () => {
mockFetch = vi.fn()
mockFetch.mockResolvedValue({ status: 200 } as Response)
global.fetch = mockFetch

// Mock https.Agent for SSL tests
const mockAgent = {} as https.Agent
Object.defineProperty(https, "Agent", {
value: vi.fn().mockImplementation(() => mockAgent),
writable: true,
})
})

afterEach(() => {
Expand Down
9 changes: 9 additions & 0 deletions packages/k8s-client/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ export default {
fileName: (format) => `index.${format}.js`, // Output file names
},
outDir: "build",
rollupOptions: {
// Treat these as externals - they won't be bundled
external: ["https"],
output: {
globals: {
https: "https",
},
},
},
},
plugins: [
dts({
Expand Down
Loading