support for private AKS clusters#181
Conversation
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull Request Overview
This PR adds support for deploying Kubernetes secrets to private AKS clusters by introducing an alternative execution path that uses az aks command invoke instead of direct API calls.
- Adds
runKubectlViaAzfunction to execute kubectl commands via Azure CLI for private clusters - Introduces conditional logic in the main
runfunction to use the new invoke command when enabled - Adds three new input parameters to control private cluster deployment mode
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/run.ts | Implements core functionality for private cluster support with temp file handling and Azure CLI execution |
| action.yml | Adds input parameters for controlling private cluster mode and cluster identification |
| test/run.test.ts | Adds comprehensive unit tests for the new private cluster functionality |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| ) | ||
| } | ||
| // Write secret to temp file | ||
| const tempFile = path.join(os.tmpdir(), `secret-${secretName}.json`) |
There was a problem hiding this comment.
This line creates a tempFile variable that is immediately overwritten on line 74. This dead code should be removed as it serves no purpose and could cause confusion.
| const tempFile = path.join(os.tmpdir(), `secret-${secretName}.json`) |
| ) | ||
| } | ||
| // Write secret to temp file | ||
| const tempFile = path.join(os.tmpdir(), `secret-${secretName}.json`) |
There was a problem hiding this comment.
This line redeclares the tempFile variable that was already declared on line 71, which will cause a compilation error in TypeScript due to duplicate variable declaration in the same scope.
| const tempFile = path.join(os.tmpdir(), `secret-${secretName}.json`) |
| '--name', | ||
| clusterName, | ||
| '--command', | ||
| `kubectl apply -f - -n ${namespace}`, |
There was a problem hiding this comment.
The kubectl command uses -f - (stdin) but then specifies --file parameter with the temp file path. This is inconsistent - either use -f - with stdin or -f ${tempFile} with the file parameter, but not both.
| `kubectl apply -f - -n ${namespace}`, | |
| `kubectl apply -f ${secretName}.json -n ${namespace}`, |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 7 comments.
Comments suppressed due to low confidence (1)
src/run.ts:71
- The initial value of tempFile is unused, since it is always overwritten.
const tempFile = path.join(os.tmpdir(), `secret-${secretName}.json`)
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const tempFile = path.join(os.tmpdir(), `secret-${secretName}.json`) | ||
| // Write secret to temp file securely | ||
| const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'secret-')); |
There was a problem hiding this comment.
The variable tempDir is declared on line 73 but never has a corresponding declaration statement. This will cause a reference error since tempDir is used in the finally block (lines 107-109) but is only accessible within the scope after line 73. The tempDir variable needs to be declared before line 73 (e.g., let tempDir: string;) to be accessible in the finally block.
| const tempFile = path.join(os.tmpdir(), `secret-${secretName}.json`) | |
| // Write secret to temp file securely | |
| const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'secret-')); | |
| let tempDir: string; | |
| // Write secret to temp file securely | |
| tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'secret-')); |
| const sec = await buildSecret(secretName, namespace, secretType) | ||
|
|
||
| if (core.getInput('use-invoke-command') === 'true') { | ||
| await runKubectlViaAz(sec, namespace, secretName) | ||
| return | ||
| } |
There was a problem hiding this comment.
The checkClusterContext() function is still called unconditionally at the start of run(), which checks for the KUBECONFIG environment variable. However, when use-invoke-command is true, the KUBECONFIG is not needed since the action uses az aks command invoke instead. This will cause the action to fail for private clusters even when the invoke command path is selected. Consider making this check conditional or moving it to only execute when the Kubernetes API path is used.
| beforeEach(() => { | ||
| jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {}) | ||
| jest.spyOn(fs, 'unlinkSync').mockImplementation(() => {}) | ||
| jest.spyOn(exec, 'exec').mockResolvedValue(0) | ||
| }) |
There was a problem hiding this comment.
The test is missing mocks for fs.mkdtempSync, fs.existsSync, and fs.rmSync which are called in the runKubectlViaAz function (lines 73, 103, 107-108 in src/run.ts). Without these mocks, the tests may attempt to create/delete actual temporary directories on the filesystem during test execution. Add mocks for these methods in the beforeEach block.
| it('calls az aks command invoke for delete and apply', async () => { | ||
| jest | ||
| .spyOn(core, 'getInput') | ||
| .mockImplementation((name: string) => | ||
| name === 'cluster-resource-group' | ||
| ? 'rg' | ||
| : name === 'cluster-name' | ||
| ? 'aks' | ||
| : '' | ||
| ) | ||
| const {runKubectlViaAz} = require('../src/run') | ||
| await runKubectlViaAz(mockSecret, 'test-ns', 'test-secret') | ||
| expect(exec.exec).toHaveBeenCalledWith( | ||
| 'az', | ||
| expect.arrayContaining([ | ||
| 'aks', | ||
| 'command', | ||
| 'invoke', | ||
| '--resource-group', | ||
| 'rg', | ||
| '--name', | ||
| 'aks' | ||
| ]) | ||
| ) | ||
| }) |
There was a problem hiding this comment.
The test only verifies that exec.exec was called with certain arguments but doesn't verify:
- That it was called exactly twice (once for delete, once for apply)
- The complete command arguments including the kubectl commands
- That the temp file operations occurred in the correct order
Consider using toHaveBeenCalledTimes(2) and more specific matchers to verify both the delete and apply commands were executed with the correct full argument lists.
| // Remove temp file if it exists | ||
| if (fs.existsSync(tempFile)) { | ||
| fs.unlinkSync(tempFile) | ||
| } |
There was a problem hiding this comment.
The cleanup logic is redundant. Lines 103-105 attempt to delete tempFile individually, but lines 107-109 already delete the entire tempDir recursively with { recursive: true, force: true }, which would remove tempFile as well. The individual file deletion (lines 103-105) is unnecessary and can be removed.
| // Remove temp file if it exists | |
| if (fs.existsSync(tempFile)) { | |
| fs.unlinkSync(tempFile) | |
| } |
| // Write secret to temp file | ||
| const tempFile = path.join(os.tmpdir(), `secret-${secretName}.json`) |
| // Write secret to temp file | ||
| const tempFile = path.join(os.tmpdir(), `secret-${secretName}.json`) |
This PR introduces full support for deploying Kubernetes secrets to private AKS clusters, which do not expose a public API server. It enables secure secret creation by leveraging az aks command invoke to execute kubectl commands directly within the cluster's internal context.
Key Features
Private Cluster Support: Uses az aks command invoke to apply or delete secrets within private AKS clusters.
Fallback to Kubernetes API: Maintains existing functionality for public clusters via the Kubernetes API client.
Temporary Secret File Handling: Writes secrets to a JSON temp file and applies them securely via kubectl apply -f.
Robust Cleanup: Ensures the temporary file is removed after execution, even if an error occurs.
Input-Driven Control: Introduces use-invoke-command input to toggle between public/private cluster deployment modes.
It also includes full unit test coverage for the private cluster pathway and related helper functions.
This addresses #82