Skip to content

Commit 2d66894

Browse files
added yaml editor for helm values (#4406)
Added yaml editor for values Signed-off-by: msivasubramaniaan <[email protected]>
1 parent 2984370 commit 2d66894

File tree

9 files changed

+400
-57
lines changed

9 files changed

+400
-57
lines changed

package-lock.json

Lines changed: 254 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,13 @@
7171
"public-ui-test": "extest setup-and-run out/test/ui/public-ui-test.js -o test/ui/settings.json -m test/ui/.mocharc.js -e ./test-resources/extensions -c max -i"
7272
},
7373
"dependencies": {
74+
"@codemirror/lang-yaml": "^6.1.1",
7475
"@kubernetes/client-node": "^0.21.0",
7576
"@redhat-developer/vscode-redhat-telemetry": "^0.8.0",
77+
"@uiw/codemirror-theme-github": "^4.23.0",
78+
"@uiw/react-codemirror": "^4.23.0",
7679
"clsx": "^2.1.1",
80+
"codemirror": "^6.0.1",
7781
"dockerode": "^4.0.2",
7882
"fs-extra": "^11.2.0",
7983
"git-up": "^7.0.0",

src/helm/helm.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,11 @@ export async function installHelmChart(
7575
repoName: string,
7676
chartName: string,
7777
version: string,
78+
yamlFilePath: string
7879
): Promise<CliExitData> {
7980
await syncHelmRepo(repoName);
8081
return await CliChannel.getInstance().executeTool(
81-
HelmCommands.installHelmChart(name, repoName, chartName, version)
82+
HelmCommands.installHelmChart(name, repoName, chartName, version, yamlFilePath)
8283
);
8384
}
8485

@@ -141,3 +142,9 @@ export async function helmSyntaxVersion(): Promise<HelmSyntaxVersion> {
141142
}
142143
return cachedVersion;
143144
}
145+
146+
export async function getYAMLValues(repoName: string, chartName: string) {
147+
return await CliChannel.getInstance().executeTool(
148+
HelmCommands.getYAMLValues(repoName, chartName), undefined, false
149+
);
150+
}

src/helm/helmCommands.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Licensed under the MIT License. See LICENSE file in the project root for license information.
44
*-----------------------------------------------------------------------------------------------*/
55

6+
import validator from 'validator';
67
import { CommandOption, CommandText } from '../base/command';
78

89
export function addHelmRepo(repoName: string, url: string): CommandText {
@@ -23,8 +24,13 @@ export function getRepos(): CommandText {
2324
return commandText;
2425
}
2526

26-
export function installHelmChart(name: string, repoName: string, chartName: string, version: string): CommandText {
27-
return new CommandText('helm', `install ${name} ${repoName}/${chartName}`, [new CommandOption('--version', version)]);
27+
export function installHelmChart(name: string, repoName: string, chartName: string, version: string, yamlFilePath: string): CommandText {
28+
const commandText = new CommandText('helm', `install ${name} ${repoName}/${chartName}`)
29+
commandText.addOption(new CommandOption('--version', version));
30+
if(yamlFilePath && !validator.isEmpty(yamlFilePath)) {
31+
commandText.addOption(new CommandOption('-f', yamlFilePath));
32+
}
33+
return commandText;
2834
}
2935

3036
export function unInstallHelmChart(name: string): CommandText {
@@ -34,3 +40,7 @@ export function unInstallHelmChart(name: string): CommandText {
3440
export function listHelmReleases(): CommandText {
3541
return new CommandText('helm', 'list', [new CommandOption('-o', 'json')]);
3642
}
43+
44+
export function getYAMLValues(repoName: string, chartName: string): CommandText {
45+
return new CommandText('helm', `show values ${repoName}/${chartName}`);
46+
}

src/webview/helm-chart/app/helmModal.tsx

Lines changed: 87 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@
66
import { Close } from '@mui/icons-material';
77
import InstallDesktopIcon from '@mui/icons-material/InstallDesktop';
88
import LoadingButton from '@mui/lab/LoadingButton';
9-
import { Alert, FormControl, FormHelperText, IconButton, InputLabel, MenuItem, Paper, Select, Stack, TextField, useMediaQuery } from '@mui/material';
9+
import { Alert, Box, FormControl, FormHelperText, IconButton, InputLabel, LinearProgress, MenuItem, Paper, Select, Stack, TextField, Theme, Typography, useMediaQuery } from '@mui/material';
1010
import React from 'react';
1111
import { Chart, ChartResponse } from '../../../helm/helmChartType';
1212
import { VSCodeMessage } from '../vsCodeMessage';
1313
import { HelmListItem } from './helmListItem';
14+
import CodeMirror from '@uiw/react-codemirror';
15+
import { yaml } from '@codemirror/lang-yaml';
16+
import { githubLight, githubDark } from '@uiw/codemirror-theme-github';
17+
import jsyaml from 'js-yaml';
1418

1519
type Message = {
1620
action: string;
@@ -22,6 +26,7 @@ export const HelmModal = React.forwardRef(
2226
props: {
2327
helmChart: ChartResponse;
2428
closeModal: () => void;
29+
theme: Theme;
2530
},
2631
ref,
2732
) => {
@@ -31,14 +36,15 @@ export const HelmModal = React.forwardRef(
3136
const [installNameErrorMessage, setInstallNameErrorMessage] = React.useState(
3237
'Please enter a name.',
3338
);
34-
3539
const [showStatus, setStatus] = React.useState<boolean>(false);
3640
const [installError, setInstallError] = React.useState<boolean>(false);
3741
const [installMsg, setInstallMsg] = React.useState<string>('');
3842
const [installLoading, setInstallLoading] = React.useState<boolean>(false);
3943

4044
const [selectedVersion, setSelectedVersion] = React.useState<Chart>(props.helmChart.chartVersions[0]);
4145
const [isInteracted, setInteracted] = React.useState(false);
46+
const [yamlValues, setYAMLValues] = React.useState<string>('');
47+
const [yamlError, setYAMLError] = React.useState<string>(undefined);
4248

4349
function respondToMessage(messageEvent: MessageEvent) {
4450
const message = messageEvent.data as Message;
@@ -63,6 +69,10 @@ export const HelmModal = React.forwardRef(
6369
}
6470
break;
6571
}
72+
case 'getYAMLValues': {
73+
setYAMLValues(message.data.yamlValues)
74+
break;
75+
}
6676
default:
6777
break;
6878
}
@@ -75,6 +85,10 @@ export const HelmModal = React.forwardRef(
7585
};
7686
}, []);
7787

88+
React.useEffect(() => {
89+
VSCodeMessage.postMessage({ action: 'getYAMLValues', data: props.helmChart });
90+
}, []);
91+
7892
const isWideEnough = useMediaQuery('(min-width: 900px)');
7993

8094
React.useEffect(() => {
@@ -101,6 +115,16 @@ export const HelmModal = React.forwardRef(
101115

102116
const isError = !versions.length || !selectedVersion;
103117

118+
const handleChange = (newValue: string) => {
119+
setYAMLError(undefined);
120+
try {
121+
jsyaml.load(newValue);
122+
setYAMLValues(newValue);
123+
} catch(e) {
124+
setYAMLError(e.message);
125+
}
126+
};
127+
104128
return (
105129
<Paper
106130
elevation={24}
@@ -109,12 +133,12 @@ export const HelmModal = React.forwardRef(
109133
top: '50%',
110134
left: '50%',
111135
width: isWideEnough ? '900px' : 'calc(100vw - 48px)',
112-
maxHeight: 'calc(100vh - 48px)',
136+
height: 'auto',
113137
transform: 'translate(-50%, -50%)',
114138
padding: 2,
115139
}}
116140
>
117-
<Stack direction='column' spacing={2}>
141+
<Stack direction='column' spacing={1} justifyContent='space-between'>
118142
<Stack
119143
direction='row'
120144
justifyContent='space-between'
@@ -170,33 +194,66 @@ export const HelmModal = React.forwardRef(
170194
);
171195
})}
172196
</Select>
173-
<Stack direction='row' justifyContent='space-between'>
174-
<FormHelperText error={isError}>{helperText}</FormHelperText>
175-
<Stack direction='row' marginTop={1} spacing={2}>
176-
<LoadingButton
177-
variant='contained'
178-
onClick={() => {
179-
setInstallLoading(true);
180-
VSCodeMessage.postMessage({
181-
action: 'install',
182-
data: {
183-
name: installName,
184-
repoName: props.helmChart.repoName,
185-
chartName: props.helmChart.chartName,
186-
version: selectedVersion.version
187-
}
188-
})
189-
}}
190-
disabled={!isInstallNameFieldValid || installName.length === 0}
191-
loading={installLoading}
192-
loadingPosition='start'
193-
startIcon={<InstallDesktopIcon />}
194-
>
195-
<span>Install</span>
196-
</LoadingButton>
197-
</Stack>
198-
</Stack>
197+
<FormHelperText error={isError}>{helperText}</FormHelperText>
199198
</FormControl>
199+
{
200+
yamlValues.length <= 0 ?
201+
<>
202+
<Box sx={{ color: '#EE0000' }}>
203+
<LinearProgress color='inherit' sx={{ height: '1rem' }} />
204+
</Box>
205+
<Typography
206+
variant='caption'
207+
component='div'
208+
color='inherit'
209+
style={{ marginTop: '3px', marginLeft: '5px', fontSize: '1em' }}
210+
>Retrieving helm values</Typography>
211+
</>
212+
:
213+
<>
214+
{
215+
yamlValues !== 'noVal' &&
216+
<Stack direction='column' spacing={1} justifyContent='space-between'>
217+
<InputLabel id='values'>Values:</InputLabel>
218+
<CodeMirror
219+
value={yamlValues}
220+
height='300px'
221+
extensions={[yaml()]}
222+
theme={props.theme?.palette.mode === 'light' ? githubLight : githubDark}
223+
onChange={handleChange}
224+
basicSetup={{
225+
lineNumbers: true,
226+
highlightActiveLine: true
227+
}} />
228+
{yamlError && <div style={{ color: '#EE0000' }}>Error: {yamlError}</div>}
229+
</Stack>
230+
}
231+
</>
232+
}
233+
<Stack direction='row' marginTop={1} spacing={2}>
234+
<LoadingButton
235+
variant='contained'
236+
onClick={() => {
237+
setInstallLoading(true);
238+
VSCodeMessage.postMessage({
239+
action: 'install',
240+
data: {
241+
name: installName,
242+
repoName: props.helmChart.repoName,
243+
chartName: props.helmChart.chartName,
244+
version: selectedVersion.version,
245+
yamlValues
246+
}
247+
});
248+
}}
249+
disabled={!isInstallNameFieldValid || installName.length === 0}
250+
loading={installLoading}
251+
loadingPosition='start'
252+
startIcon={<InstallDesktopIcon />}
253+
>
254+
<span>Install</span>
255+
</LoadingButton>
256+
</Stack>
200257
{showStatus && (
201258
!installError ? < Alert severity='info'>
202259
{installMsg} `{installName}`

src/webview/helm-chart/app/helmSearch.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*-----------------------------------------------------------------------------------------------*/
55

66
import React from 'react';
7-
import { Alert, Checkbox, Divider, FormControlLabel, FormGroup, IconButton, InputAdornment, Modal, Pagination, Stack, TextField, Tooltip, Typography } from '@mui/material';
7+
import { Alert, Checkbox, Divider, FormControlLabel, FormGroup, IconButton, InputAdornment, Modal, Pagination, Stack, TextField, Theme, Tooltip, Typography } from '@mui/material';
88
import { Close, Search } from '@mui/icons-material';
99
import { HelmListItem } from './helmListItem';
1010
import { ChartResponse, HelmRepo } from '../../../helm/helmChartType';
@@ -161,7 +161,11 @@ function SearchBar(props: {
161161
);
162162
}
163163

164-
export function HelmSearch() {
164+
type HelmSearchProps = {
165+
theme: Theme;
166+
};
167+
168+
export function HelmSearch(props: HelmSearchProps) {
165169
const ITEMS_PER_PAGE = 18;
166170
const [isSomeHelmChartsRetrieved, setSomeHelmChartsRetrieved] = React.useState(false);
167171
const [helmRepos, setHelmRepos] = React.useState<HelmRepo[]>([]);
@@ -374,6 +378,7 @@ export function HelmSearch() {
374378
closeModal={() => {
375379
setselectedHelmChart((_) => undefined);
376380
}}
381+
theme={props.theme}
377382
/>
378383
</Modal>
379384
</>

src/webview/helm-chart/app/home.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export const Home = () => {
3838
return (
3939
<ThemeProvider theme={theme}>
4040
<Container maxWidth='lg' sx={{ height: '100%', paddingTop: '1em', paddingBottom: '1em'}}>
41-
<HelmSearch />
41+
<HelmSearch theme={theme}/>
4242
</Container>
4343
</ThemeProvider>
4444
);

src/webview/helm-chart/helmChartLoader.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import * as JSYAML from 'js-yaml';
66
import * as path from 'path';
77
import * as vscode from 'vscode';
8+
import * as fs from 'fs';
9+
import * as tmp from 'tmp';
810
import { OpenShiftExplorer } from '../../explorer';
911
import * as Helm from '../../helm/helm';
1012
import { Chart, ChartResponse, HelmRepo } from '../../helm/helmChartType';
@@ -14,6 +16,7 @@ import { Progress } from '../../util/progress';
1416
import { vsCommand } from '../../vscommand';
1517
import { validateName } from '../common-ext/createComponentHelpers';
1618
import { loadWebviewHtml } from '../common-ext/utils';
19+
import { promisify } from 'util';
1720

1821
let panel: vscode.WebviewPanel;
1922
const helmCharts: ChartResponse[] = [];
@@ -38,8 +41,14 @@ export class HelmCommand {
3841
message: 'Installing'
3942
}
4043
});
44+
45+
//write temp yaml file for values
46+
const tmpFolder = vscode.Uri.parse(await promisify(tmp.dir)());
47+
const tempFilePath = path.join(tmpFolder.fsPath, `helmValues-${Date.now()}.yaml`);
48+
fs.writeFileSync(tempFilePath, event.data.yamlValues, 'utf8');
49+
4150
void Progress.execFunctionWithProgress(`Installing the chart ${event.data.name}`, async () => {
42-
await Helm.installHelmChart(event.data.name, event.data.repoName, event.data.chartName, event.data.version);
51+
await Helm.installHelmChart(event.data.name, event.data.repoName, event.data.chartName, event.data.version, tempFilePath);
4352
}).then(() => {
4453
void panel.webview.postMessage({
4554
action: 'installStatus',
@@ -59,6 +68,8 @@ export class HelmCommand {
5968
message: message.substring(message.indexOf('INSTALLATION FAILED:') + 'INSTALLATION FAILED:'.length)
6069
}
6170
});
71+
}).finally(() => {
72+
fs.rm(tmpFolder.fsPath, { force: true, recursive: true }, undefined);
6273
});
6374
}
6475

@@ -70,7 +81,7 @@ export class HelmCommand {
7081
}
7182
}
7283

73-
function helmChartMessageListener(event: any): void {
84+
async function helmChartMessageListener(event: any): Promise<void> {
7485
switch (event?.action) {
7586
case 'init':
7687
void panel.webview.postMessage({
@@ -95,6 +106,19 @@ function helmChartMessageListener(event: any): void {
95106
});
96107
break;
97108
}
109+
case 'getYAMLValues': {
110+
const yamlValues = await Helm.getYAMLValues(event.data.repoName as string, event.data.chartName as string);
111+
if (yamlValues) {
112+
void panel.webview.postMessage({
113+
action: 'getYAMLValues',
114+
data: {
115+
helmChart: event.data,
116+
yamlValues: yamlValues.stdout.length > 0 ? yamlValues.stdout : 'noVal'
117+
},
118+
});
119+
}
120+
break;
121+
}
98122
case 'getProviderTypes': {
99123
const types: string[] = [];
100124
helmCharts.map((helm: ChartResponse) => {

test/integration/helm.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ suite('helm integration', function () {
5555
});
5656

5757
test('installs a chart as a release', async function () {
58-
await Helm.installHelmChart(RELEASE_NAME, REPO_NAME, CHART_NAME, CHART_VERSION);
58+
await Helm.installHelmChart(RELEASE_NAME, REPO_NAME, CHART_NAME, CHART_VERSION, undefined);
5959
const releases = await Helm.getHelmReleases();
6060
const sampleChartRelease = releases.find((release) => release.name === RELEASE_NAME);
6161
expect(sampleChartRelease).to.exist;

0 commit comments

Comments
 (0)