Skip to content

Commit

Permalink
feat(stages/bakeManifests): add helmfile support
Browse files Browse the repository at this point in the history
  • Loading branch information
thameezb committed Jun 6, 2023
1 parent 95d5f94 commit ca760ed
Show file tree
Hide file tree
Showing 6 changed files with 423 additions and 3 deletions.
5 changes: 5 additions & 0 deletions packages/core/src/help/help.contents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,11 @@ const helpContents: { [key: string]: string } = {
'pipeline.config.bake.manifest.kustomize.filePath': `
<p>This is the relative path to the kustomization.yaml file within your Git repo.</p>
<p>e.g.: <b>examples/wordpress/mysql/kustomization.yaml</b></p>`,
'pipeline.config.bake.manifest.helmfile.filePath': `
<p>This is the relative path to the directory containing the helmfile.yaml file within your Git repo.</p>
<p>e.g.: <b>chart/helmfile.yml</b></p>`,
'pipeline.config.bake.manifest.helmfile.name':
'<p> Name is used to set the expected artifact in the Produces Artifact section. </p>',
'pipeline.config.bake.cf.manifest.name':
'<p> Name should be the same as the expected artifact in the Produces Artifact section. </p>',
'pipeline.config.bake.cf.manifest.templateArtifact': `
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import React from 'react';

import { BakeManifestStageForm, validateProducedArtifacts } from './BakeManifestStageForm';
import { FormikStageConfig } from '../FormikStageConfig';
import { HELM_RENDERERS } from './ManifestRenderers';
import { HELM_RENDERERS, HELMFILE_RENDERER } from './ManifestRenderers';
import type { IStageConfigProps } from '../common';
import type { IExpectedArtifact, IStage } from '../../../../domain';
import { FormValidator } from '../../../../presentation';
Expand Down Expand Up @@ -46,6 +46,9 @@ export function validateBakeManifestStage(stage: IStage): FormikErrors<IStage> {
if (HELM_RENDERERS.includes(stage.templateRenderer)) {
formValidator.field('outputName', 'Name').required();
}
if (HELMFILE_RENDERER === stage.templateRenderer) {
formValidator.field('outputName', 'Name').required();
}

return formValidator.validateForm();
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { isNil } from 'lodash';
import React from 'react';

import type { IFormikStageConfigInjectedProps } from '../FormikStageConfig';
import { HELM_RENDERERS, KUSTOMIZE_RENDERERS } from './ManifestRenderers';
import { HELM_RENDERERS, HELMFILE_RENDERER, KUSTOMIZE_RENDERERS } from './ManifestRenderers';
import { ExpectedArtifactService } from '../../../../artifact';
import { StageConfigField } from '../common';
import type { IExpectedArtifact } from '../../../../domain';
import { BakeHelmConfigForm } from './helm/BakeHelmConfigForm';
import { BakeHelmfileConfigForm } from './helmfile/BakeHelmfileConfigForm';
import { BakeKustomizeConfigForm } from './kustomize/BakeKustomizeConfigForm';
import { ReactSelectInput } from '../../../../presentation';
import { BASE_64_ARTIFACT_ACCOUNT, BASE_64_ARTIFACT_TYPE } from '../../triggers/artifacts/base64/Base64ArtifactEditor';
Expand All @@ -32,10 +33,13 @@ export function BakeManifestStageForm({ application, formik, pipeline }: IFormik
if (HELM_RENDERERS.includes(stage.templateRenderer) && !isNil(stage.inputArtifact)) {
formik.setFieldValue('inputArtifact', null);
}
if (HELMFILE_RENDERER === stage.templateRenderer && !isNil(stage.inputArtifact)) {
formik.setFieldValue('inputArtifact', null);
}
}, [stage.templateRenderer]);

const templateRenderers = React.useMemo(() => {
return [...KUSTOMIZE_RENDERERS, ...HELM_RENDERERS];
return [...KUSTOMIZE_RENDERERS, ...HELM_RENDERERS, HELMFILE_RENDERER];
}, []);

return (
Expand All @@ -62,6 +66,9 @@ export function BakeManifestStageForm({ application, formik, pipeline }: IFormik
{HELM_RENDERERS.includes(stage.templateRenderer) && (
<BakeHelmConfigForm pipeline={pipeline} application={application} formik={formik} />
)}
{HELMFILE_RENDERER === stage.templateRenderer && (
<BakeHelmfileConfigForm pipeline={pipeline} application={application} formik={formik} />
)}
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
export enum ManifestRenderers {
HELM2 = 'HELM2',
HELM3 = 'HELM3',
HELMFILE = 'HELMFILE',
KUSTOMIZE = 'KUSTOMIZE',
KUSTOMIZE4 = 'KUSTOMIZE4',
}

export const HELM_RENDERERS: Readonly<ManifestRenderers[]> = [ManifestRenderers.HELM2, ManifestRenderers.HELM3];
export const HELMFILE_RENDERER: Readonly<ManifestRenderers> = ManifestRenderers.HELMFILE;
export const KUSTOMIZE_RENDERERS: Readonly<ManifestRenderers[]> = [
ManifestRenderers.KUSTOMIZE,
ManifestRenderers.KUSTOMIZE4,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { mock } from 'angular';
import { mount } from 'enzyme';
import React from 'react';

import { StageConfigField } from '../../../..';
import { BakeHelmfileConfigForm } from './BakeHelmfileConfigForm';
import { AccountService } from '../../../../../account';
import { ApplicationModelBuilder } from '../../../../../application';
import { ExpectedArtifactService } from '../../../../../artifact';
import type { IExpectedArtifact, IStage } from '../../../../../domain';
import { SpinFormik } from '../../../../../presentation';
import { REACT_MODULE } from '../../../../../reactShims';

describe('<BakeHelmfileConfigForm />', () => {
beforeEach(mock.module(REACT_MODULE));
beforeEach(mock.inject());

const helmfileFilePathFieldName = 'Helmfile File Path';

const getProps = () => {
return {
application: ApplicationModelBuilder.createApplicationForTests('my-application'),
pipeline: {
application: 'my-application',
id: 'pipeline-id',
limitConcurrent: true,
keepWaitingPipelines: true,
name: 'My Pipeline',
parameterConfig: [],
stages: [],
triggers: [],
},
} as any;
};

beforeEach(() =>
spyOn(AccountService, 'getArtifactAccounts').and.returnValue(
Promise.resolve([
{ name: 'gitrepo', types: ['something-else', 'git/repo'] },
{ name: 'notgitrepo', types: ['something-else'] },
]),
),
);

it('renders the helmfile file path element when the template artifact is from an account that handles git/repo artifacts', async () => {
const stage = ({
inputArtifacts: [{ account: 'gitrepo' }],
} as unknown) as IStage;

const props = getProps();

const component = mount(
<SpinFormik
initialValues={stage}
onSubmit={() => null}
validate={() => null}
render={(formik) => <BakeHelmfileConfigForm {...props} formik={formik} />}
/>,
);

await new Promise((resolve) => setTimeout(resolve)); // wait one js tick for promise to resolve
component.setProps({}); // force a re-render

expect(component.find(StageConfigField).findWhere((x) => x.text() === helmfileFilePathFieldName).length).toBe(1);
});

it('does not render the helmfile file path element when the template artifact is from an account that does not handle git/repo artifacts', async () => {
const stage = ({
inputArtifacts: [{ account: 'notgitrepo' }],
} as unknown) as IStage;

const props = getProps();

const component = mount(
<SpinFormik
initialValues={stage}
onSubmit={() => null}
validate={() => null}
render={(formik) => <BakeHelmfileConfigForm {...props} formik={formik} />}
/>,
);

await new Promise((resolve) => setTimeout(resolve)); // wait one js tick for promise to resolve
component.setProps({}); // force a re-render

expect(component.find(StageConfigField).findWhere((x) => x.text() === helmfileFilePathFieldName).length).toBe(0);
});

it('render the helmfile file path if the id of the git artifact is given but the account value does not exist', async () => {
const expectedArtifactDisplayName = 'test-artifact';
const expectedArtifactId = 'test-artifact-id';
const expectedGitArtifact: IExpectedArtifact = {
defaultArtifact: {
customKind: true,
id: 'defaultArtifact-id',
},
displayName: expectedArtifactDisplayName,
id: expectedArtifactId,
matchArtifact: {
artifactAccount: 'gitrepo',
id: expectedArtifactId,
reference: 'git repo',
type: 'git/repo',
version: 'master',
},
useDefaultArtifact: false,
usePriorArtifact: false,
};
const stage = ({
inputArtifacts: [{ id: expectedArtifactId }],
} as unknown) as IStage;

spyOn(ExpectedArtifactService, 'getExpectedArtifactsAvailableToStage').and.returnValue([expectedGitArtifact]);

const props = getProps();

const component = mount(
<SpinFormik
initialValues={stage}
onSubmit={() => null}
validate={() => null}
render={(formik) => <BakeHelmfileConfigForm {...props} formik={formik} />}
/>,
);

await new Promise((resolve) => setTimeout(resolve)); // wait one js tick for promise to resolve
component.setProps({}); // force a re-render

expect(component.find('.Select-value-label > span').text().includes(expectedArtifactDisplayName)).toBe(true);
expect(component.find(StageConfigField).findWhere((x) => x.text() === helmfileFilePathFieldName).length).toBe(1);
});
});
Loading

0 comments on commit ca760ed

Please sign in to comment.