@spinnaker/core 0.24.1 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/dist/application/config/defaultTagFilter/DefaultTagFilterConfig.d.ts +27 -0
- package/dist/application/config/defaultTagFilter/defaultTagFilterConfig.component.d.ts +1 -0
- package/dist/config/settings.d.ts +1 -0
- package/dist/domain/IArtifact.d.ts +2 -0
- package/dist/domain/ITrigger.d.ts +5 -0
- package/dist/index.js +36 -36
- package/dist/index.js.map +1 -1
- package/dist/manifest/ManifestYaml.d.ts +10 -4
- package/dist/pipeline/config/stages/bakeManifest/ManifestRenderers.d.ts +2 -0
- package/dist/pipeline/config/stages/bakeManifest/helmfile/BakeHelmfileConfigForm.d.ts +18 -0
- package/dist/pipeline/config/stages/bakeManifest/utils/getBakedArtifacts.d.ts +4 -0
- package/dist/pipeline/config/stages/bakeManifest/utils/getContentReference.d.ts +1 -0
- package/dist/pipeline/config/triggers/artifacts/ArtifactService.d.ts +3 -0
- package/dist/pipeline/config/triggers/cdevents/CDEventsTrigger.d.ts +7 -0
- package/dist/pipeline/config/triggers/cdevents/cdevents.trigger.d.ts +1 -0
- package/dist/pipeline/config/triggers/index.d.ts +1 -0
- package/dist/pipeline/executions/Executions.d.ts +1 -0
- package/dist/pipeline/index.d.ts +2 -0
- package/dist/presentation/forms/inputs/NumberConcurrencyInput.d.ts +7 -0
- package/package.json +2 -2
- package/src/application/config/applicationConfig.controller.js +26 -0
- package/src/application/config/applicationConfig.view.html +9 -0
- package/src/application/config/defaultTagFilter/DefaultTagFilterConfig.spec.tsx +75 -0
- package/src/application/config/defaultTagFilter/DefaultTagFilterConfig.tsx +161 -0
- package/src/application/config/defaultTagFilter/defaultTagFilterConfig.component.ts +15 -0
- package/src/application/config/defaultTagFilter/defaultTagFilterConfig.less +8 -0
- package/src/artifact/ArtifactIconService.ts +1 -0
- package/src/artifact/ArtifactTypes.ts +1 -0
- package/src/config/settings.ts +1 -0
- package/src/domain/IArtifact.ts +3 -0
- package/src/domain/ITrigger.ts +4 -0
- package/src/help/help.contents.ts +10 -0
- package/src/manifest/ManifestYaml.tsx +29 -7
- package/src/pipeline/config/stages/bakeManifest/BakeManifestConfig.tsx +4 -1
- package/src/pipeline/config/stages/bakeManifest/BakeManifestDetailsTab.tsx +24 -12
- package/src/pipeline/config/stages/bakeManifest/BakeManifestStageForm.tsx +9 -2
- package/src/pipeline/config/stages/bakeManifest/ManifestRenderers.ts +2 -0
- package/src/pipeline/config/stages/bakeManifest/helmfile/BakeHelmfileConfigForm.spec.tsx +132 -0
- package/src/pipeline/config/stages/bakeManifest/helmfile/BakeHelmfileConfigForm.tsx +271 -0
- package/src/pipeline/config/stages/bakeManifest/utils/getBakedArtifacts.ts +13 -0
- package/src/pipeline/config/stages/bakeManifest/utils/getContentReference.ts +3 -0
- package/src/pipeline/config/triggers/artifacts/ArtifactService.ts +4 -0
- package/src/pipeline/config/triggers/cdevents/CDEventsTrigger.tsx +48 -0
- package/src/pipeline/config/triggers/cdevents/cdevents.trigger.ts +19 -0
- package/src/pipeline/config/triggers/index.ts +1 -0
- package/src/pipeline/create/CreatePipelineModal.tsx +1 -1
- package/src/pipeline/details/StageFailureMessage.tsx +10 -24
- package/src/pipeline/executions/Executions.tsx +13 -0
- package/src/pipeline/index.ts +2 -0
- package/src/pipeline/status/ExecutionCancellationReason.tsx +1 -1
- package/src/presentation/forms/inputs/NumberConcurrencyInput.tsx +29 -0
|
@@ -3,10 +3,12 @@ import React from 'react';
|
|
|
3
3
|
import type { IExecutionDetailsSectionProps } from '../common';
|
|
4
4
|
import { ExecutionDetailsSection } from '../common';
|
|
5
5
|
import { StageFailureMessage } from '../../../details';
|
|
6
|
-
import
|
|
6
|
+
import { ARTIFACT_TYPE_EMBEDDED } from '../../../../domain';
|
|
7
7
|
import { ManifestYaml } from '../../../../manifest';
|
|
8
8
|
import { Overridable } from '../../../../overrideRegistry';
|
|
9
9
|
import { decodeUnicodeBase64 } from '../../../../utils';
|
|
10
|
+
import { getBakedArtifacts } from './utils/getBakedArtifacts';
|
|
11
|
+
import { getContentReference } from './utils/getContentReference';
|
|
10
12
|
|
|
11
13
|
@Overridable('bakeManifest.bakeManifestDetailsTab')
|
|
12
14
|
export class BakeManifestDetailsTab extends React.Component<IExecutionDetailsSectionProps> {
|
|
@@ -14,20 +16,30 @@ export class BakeManifestDetailsTab extends React.Component<IExecutionDetailsSec
|
|
|
14
16
|
|
|
15
17
|
public render() {
|
|
16
18
|
const { current, name, stage } = this.props;
|
|
17
|
-
const bakedArtifacts
|
|
18
|
-
|
|
19
|
-
);
|
|
19
|
+
const bakedArtifacts = getBakedArtifacts(stage.context);
|
|
20
|
+
|
|
20
21
|
return (
|
|
21
22
|
<ExecutionDetailsSection name={name} current={current}>
|
|
22
23
|
<StageFailureMessage stage={stage} message={stage.failureMessage} />
|
|
23
|
-
{bakedArtifacts.map((artifact, i) =>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
{bakedArtifacts.map((artifact, i) => {
|
|
25
|
+
const linkName = bakedArtifacts.length > 1 ? `Baked Manifest ${i} YAML` : 'Baked Manifest YAML';
|
|
26
|
+
|
|
27
|
+
return artifact.type === ARTIFACT_TYPE_EMBEDDED ? (
|
|
28
|
+
<ManifestYaml
|
|
29
|
+
key={i}
|
|
30
|
+
linkName={linkName}
|
|
31
|
+
modalTitle="Baked Manifest"
|
|
32
|
+
manifestText={decodeUnicodeBase64(artifact.reference)}
|
|
33
|
+
/>
|
|
34
|
+
) : (
|
|
35
|
+
<ManifestYaml
|
|
36
|
+
key={i}
|
|
37
|
+
linkName={linkName}
|
|
38
|
+
modalTitle="Baked Manifest"
|
|
39
|
+
manifestUri={getContentReference(artifact.reference)}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
})}
|
|
31
43
|
</ExecutionDetailsSection>
|
|
32
44
|
);
|
|
33
45
|
}
|
|
@@ -2,11 +2,12 @@ import { isNil } from 'lodash';
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
|
|
4
4
|
import type { IFormikStageConfigInjectedProps } from '../FormikStageConfig';
|
|
5
|
-
import { HELM_RENDERERS, KUSTOMIZE_RENDERERS } from './ManifestRenderers';
|
|
5
|
+
import { HELM_RENDERERS, HELMFILE_RENDERER, KUSTOMIZE_RENDERERS } from './ManifestRenderers';
|
|
6
6
|
import { ExpectedArtifactService } from '../../../../artifact';
|
|
7
7
|
import { StageConfigField } from '../common';
|
|
8
8
|
import type { IExpectedArtifact } from '../../../../domain';
|
|
9
9
|
import { BakeHelmConfigForm } from './helm/BakeHelmConfigForm';
|
|
10
|
+
import { BakeHelmfileConfigForm } from './helmfile/BakeHelmfileConfigForm';
|
|
10
11
|
import { BakeKustomizeConfigForm } from './kustomize/BakeKustomizeConfigForm';
|
|
11
12
|
import { ReactSelectInput } from '../../../../presentation';
|
|
12
13
|
import { BASE_64_ARTIFACT_ACCOUNT, BASE_64_ARTIFACT_TYPE } from '../../triggers/artifacts/base64/Base64ArtifactEditor';
|
|
@@ -32,10 +33,13 @@ export function BakeManifestStageForm({ application, formik, pipeline }: IFormik
|
|
|
32
33
|
if (HELM_RENDERERS.includes(stage.templateRenderer) && !isNil(stage.inputArtifact)) {
|
|
33
34
|
formik.setFieldValue('inputArtifact', null);
|
|
34
35
|
}
|
|
36
|
+
if (HELMFILE_RENDERER === stage.templateRenderer && !isNil(stage.inputArtifact)) {
|
|
37
|
+
formik.setFieldValue('inputArtifact', null);
|
|
38
|
+
}
|
|
35
39
|
}, [stage.templateRenderer]);
|
|
36
40
|
|
|
37
41
|
const templateRenderers = React.useMemo(() => {
|
|
38
|
-
return [...KUSTOMIZE_RENDERERS, ...HELM_RENDERERS];
|
|
42
|
+
return [...KUSTOMIZE_RENDERERS, ...HELM_RENDERERS, HELMFILE_RENDERER];
|
|
39
43
|
}, []);
|
|
40
44
|
|
|
41
45
|
return (
|
|
@@ -62,6 +66,9 @@ export function BakeManifestStageForm({ application, formik, pipeline }: IFormik
|
|
|
62
66
|
{HELM_RENDERERS.includes(stage.templateRenderer) && (
|
|
63
67
|
<BakeHelmConfigForm pipeline={pipeline} application={application} formik={formik} />
|
|
64
68
|
)}
|
|
69
|
+
{HELMFILE_RENDERER === stage.templateRenderer && (
|
|
70
|
+
<BakeHelmfileConfigForm pipeline={pipeline} application={application} formik={formik} />
|
|
71
|
+
)}
|
|
65
72
|
</div>
|
|
66
73
|
</div>
|
|
67
74
|
);
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
export enum ManifestRenderers {
|
|
2
2
|
HELM2 = 'HELM2',
|
|
3
3
|
HELM3 = 'HELM3',
|
|
4
|
+
HELMFILE = 'HELMFILE',
|
|
4
5
|
KUSTOMIZE = 'KUSTOMIZE',
|
|
5
6
|
KUSTOMIZE4 = 'KUSTOMIZE4',
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
export const HELM_RENDERERS: Readonly<ManifestRenderers[]> = [ManifestRenderers.HELM2, ManifestRenderers.HELM3];
|
|
10
|
+
export const HELMFILE_RENDERER: Readonly<ManifestRenderers> = ManifestRenderers.HELMFILE;
|
|
9
11
|
export const KUSTOMIZE_RENDERERS: Readonly<ManifestRenderers[]> = [
|
|
10
12
|
ManifestRenderers.KUSTOMIZE,
|
|
11
13
|
ManifestRenderers.KUSTOMIZE4,
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { mock } from 'angular';
|
|
2
|
+
import { mount } from 'enzyme';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
|
|
5
|
+
import { StageConfigField } from '../../../..';
|
|
6
|
+
import { BakeHelmfileConfigForm } from './BakeHelmfileConfigForm';
|
|
7
|
+
import { AccountService } from '../../../../../account';
|
|
8
|
+
import { ApplicationModelBuilder } from '../../../../../application';
|
|
9
|
+
import { ExpectedArtifactService } from '../../../../../artifact';
|
|
10
|
+
import type { IExpectedArtifact, IStage } from '../../../../../domain';
|
|
11
|
+
import { SpinFormik } from '../../../../../presentation';
|
|
12
|
+
import { REACT_MODULE } from '../../../../../reactShims';
|
|
13
|
+
|
|
14
|
+
describe('<BakeHelmfileConfigForm />', () => {
|
|
15
|
+
beforeEach(mock.module(REACT_MODULE));
|
|
16
|
+
beforeEach(mock.inject());
|
|
17
|
+
|
|
18
|
+
const helmfileFilePathFieldName = 'Helmfile File Path';
|
|
19
|
+
|
|
20
|
+
const getProps = () => {
|
|
21
|
+
return {
|
|
22
|
+
application: ApplicationModelBuilder.createApplicationForTests('my-application'),
|
|
23
|
+
pipeline: {
|
|
24
|
+
application: 'my-application',
|
|
25
|
+
id: 'pipeline-id',
|
|
26
|
+
limitConcurrent: true,
|
|
27
|
+
keepWaitingPipelines: true,
|
|
28
|
+
name: 'My Pipeline',
|
|
29
|
+
parameterConfig: [],
|
|
30
|
+
stages: [],
|
|
31
|
+
triggers: [],
|
|
32
|
+
},
|
|
33
|
+
} as any;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
beforeEach(() =>
|
|
37
|
+
spyOn(AccountService, 'getArtifactAccounts').and.returnValue(
|
|
38
|
+
Promise.resolve([
|
|
39
|
+
{ name: 'gitrepo', types: ['something-else', 'git/repo'] },
|
|
40
|
+
{ name: 'notgitrepo', types: ['something-else'] },
|
|
41
|
+
]),
|
|
42
|
+
),
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
it('renders the helmfile file path element when the template artifact is from an account that handles git/repo artifacts', async () => {
|
|
46
|
+
const stage = ({
|
|
47
|
+
inputArtifacts: [{ account: 'gitrepo' }],
|
|
48
|
+
} as unknown) as IStage;
|
|
49
|
+
|
|
50
|
+
const props = getProps();
|
|
51
|
+
|
|
52
|
+
const component = mount(
|
|
53
|
+
<SpinFormik
|
|
54
|
+
initialValues={stage}
|
|
55
|
+
onSubmit={() => null}
|
|
56
|
+
validate={() => null}
|
|
57
|
+
render={(formik) => <BakeHelmfileConfigForm {...props} formik={formik} />}
|
|
58
|
+
/>,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
await new Promise((resolve) => setTimeout(resolve)); // wait one js tick for promise to resolve
|
|
62
|
+
component.setProps({}); // force a re-render
|
|
63
|
+
|
|
64
|
+
expect(component.find(StageConfigField).findWhere((x) => x.text() === helmfileFilePathFieldName).length).toBe(1);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
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 () => {
|
|
68
|
+
const stage = ({
|
|
69
|
+
inputArtifacts: [{ account: 'notgitrepo' }],
|
|
70
|
+
} as unknown) as IStage;
|
|
71
|
+
|
|
72
|
+
const props = getProps();
|
|
73
|
+
|
|
74
|
+
const component = mount(
|
|
75
|
+
<SpinFormik
|
|
76
|
+
initialValues={stage}
|
|
77
|
+
onSubmit={() => null}
|
|
78
|
+
validate={() => null}
|
|
79
|
+
render={(formik) => <BakeHelmfileConfigForm {...props} formik={formik} />}
|
|
80
|
+
/>,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
await new Promise((resolve) => setTimeout(resolve)); // wait one js tick for promise to resolve
|
|
84
|
+
component.setProps({}); // force a re-render
|
|
85
|
+
|
|
86
|
+
expect(component.find(StageConfigField).findWhere((x) => x.text() === helmfileFilePathFieldName).length).toBe(0);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('render the helmfile file path if the id of the git artifact is given but the account value does not exist', async () => {
|
|
90
|
+
const expectedArtifactDisplayName = 'test-artifact';
|
|
91
|
+
const expectedArtifactId = 'test-artifact-id';
|
|
92
|
+
const expectedGitArtifact: IExpectedArtifact = {
|
|
93
|
+
defaultArtifact: {
|
|
94
|
+
customKind: true,
|
|
95
|
+
id: 'defaultArtifact-id',
|
|
96
|
+
},
|
|
97
|
+
displayName: expectedArtifactDisplayName,
|
|
98
|
+
id: expectedArtifactId,
|
|
99
|
+
matchArtifact: {
|
|
100
|
+
artifactAccount: 'gitrepo',
|
|
101
|
+
id: expectedArtifactId,
|
|
102
|
+
reference: 'git repo',
|
|
103
|
+
type: 'git/repo',
|
|
104
|
+
version: 'master',
|
|
105
|
+
},
|
|
106
|
+
useDefaultArtifact: false,
|
|
107
|
+
usePriorArtifact: false,
|
|
108
|
+
};
|
|
109
|
+
const stage = ({
|
|
110
|
+
inputArtifacts: [{ id: expectedArtifactId }],
|
|
111
|
+
} as unknown) as IStage;
|
|
112
|
+
|
|
113
|
+
spyOn(ExpectedArtifactService, 'getExpectedArtifactsAvailableToStage').and.returnValue([expectedGitArtifact]);
|
|
114
|
+
|
|
115
|
+
const props = getProps();
|
|
116
|
+
|
|
117
|
+
const component = mount(
|
|
118
|
+
<SpinFormik
|
|
119
|
+
initialValues={stage}
|
|
120
|
+
onSubmit={() => null}
|
|
121
|
+
validate={() => null}
|
|
122
|
+
render={(formik) => <BakeHelmfileConfigForm {...props} formik={formik} />}
|
|
123
|
+
/>,
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
await new Promise((resolve) => setTimeout(resolve)); // wait one js tick for promise to resolve
|
|
127
|
+
component.setProps({}); // force a re-render
|
|
128
|
+
|
|
129
|
+
expect(component.find('.Select-value-label > span').text().includes(expectedArtifactDisplayName)).toBe(true);
|
|
130
|
+
expect(component.find(StageConfigField).findWhere((x) => x.text() === helmfileFilePathFieldName).length).toBe(1);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import type { IFormikStageConfigInjectedProps } from '../../FormikStageConfig';
|
|
4
|
+
import { AccountService } from '../../../../../account';
|
|
5
|
+
import {
|
|
6
|
+
ArtifactTypePatterns,
|
|
7
|
+
excludeAllTypesExcept,
|
|
8
|
+
ExpectedArtifactService,
|
|
9
|
+
StageArtifactSelectorDelegate,
|
|
10
|
+
} from '../../../../../artifact';
|
|
11
|
+
import { StageConfigField } from '../../common/stageConfigField/StageConfigField';
|
|
12
|
+
import type { IArtifact, IExpectedArtifact } from '../../../../../domain';
|
|
13
|
+
import { MapEditor } from '../../../../../forms';
|
|
14
|
+
import { CheckboxInput, TextInput } from '../../../../../presentation';
|
|
15
|
+
|
|
16
|
+
export interface IBakeHelmfileConfigFormState {
|
|
17
|
+
gitRepoArtifactAccountNames: string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class BakeHelmfileConfigForm extends React.Component<
|
|
21
|
+
IFormikStageConfigInjectedProps,
|
|
22
|
+
IBakeHelmfileConfigFormState
|
|
23
|
+
> {
|
|
24
|
+
constructor(props: IFormikStageConfigInjectedProps) {
|
|
25
|
+
super(props);
|
|
26
|
+
this.state = { gitRepoArtifactAccountNames: [] };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private static readonly excludedArtifactTypes = excludeAllTypesExcept(
|
|
30
|
+
ArtifactTypePatterns.BITBUCKET_FILE,
|
|
31
|
+
ArtifactTypePatterns.CUSTOM_OBJECT,
|
|
32
|
+
ArtifactTypePatterns.EMBEDDED_BASE64,
|
|
33
|
+
ArtifactTypePatterns.GCS_OBJECT,
|
|
34
|
+
ArtifactTypePatterns.GIT_REPO,
|
|
35
|
+
ArtifactTypePatterns.GITHUB_FILE,
|
|
36
|
+
ArtifactTypePatterns.GITLAB_FILE,
|
|
37
|
+
ArtifactTypePatterns.S3_OBJECT,
|
|
38
|
+
ArtifactTypePatterns.HELM_CHART,
|
|
39
|
+
ArtifactTypePatterns.HTTP_FILE,
|
|
40
|
+
ArtifactTypePatterns.ORACLE_OBJECT,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
public componentDidMount() {
|
|
44
|
+
const stage = this.props.formik.values;
|
|
45
|
+
if (stage.inputArtifacts && stage.inputArtifacts.length === 0) {
|
|
46
|
+
this.props.formik.setFieldValue('inputArtifacts', [
|
|
47
|
+
{
|
|
48
|
+
account: '',
|
|
49
|
+
id: '',
|
|
50
|
+
},
|
|
51
|
+
]);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// If the Expected Artifact id is provided but the account is not, then attempt to find the artifact from
|
|
55
|
+
// upstream stages and set the account value.
|
|
56
|
+
// This is needed because helmfile file path field will need to be rendered if the artifact has a git repo account type
|
|
57
|
+
const expectedArtifact = this.getInputArtifact(stage, 0);
|
|
58
|
+
if (expectedArtifact.id && !expectedArtifact.account) {
|
|
59
|
+
const availableArtifacts = ExpectedArtifactService.getExpectedArtifactsAvailableToStage(
|
|
60
|
+
stage,
|
|
61
|
+
this.props.pipeline,
|
|
62
|
+
);
|
|
63
|
+
const expectedMatchedArtifact = availableArtifacts.find((a) => a.id === expectedArtifact.id);
|
|
64
|
+
if (expectedMatchedArtifact && expectedMatchedArtifact.matchArtifact) {
|
|
65
|
+
this.props.formik.setFieldValue(
|
|
66
|
+
`inputArtifacts[0].account`,
|
|
67
|
+
expectedMatchedArtifact.matchArtifact.artifactAccount,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
AccountService.getArtifactAccounts().then((artifactAccounts) => {
|
|
73
|
+
this.setState({
|
|
74
|
+
gitRepoArtifactAccountNames: artifactAccounts
|
|
75
|
+
.filter((account) => account.types.some((type) => ArtifactTypePatterns.GIT_REPO.test(type)))
|
|
76
|
+
.map((account) => account.name),
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private onTemplateArtifactEdited = (artifact: IArtifact, index: number) => {
|
|
82
|
+
this.props.formik.setFieldValue(`inputArtifacts[${index}].id`, null);
|
|
83
|
+
this.props.formik.setFieldValue(`inputArtifacts[${index}].artifact`, artifact);
|
|
84
|
+
this.props.formik.setFieldValue(`inputArtifacts[${index}].account`, artifact.artifactAccount);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
private onTemplateArtifactSelected = (artifact: IExpectedArtifact, index: number) => {
|
|
88
|
+
this.props.formik.setFieldValue(`inputArtifacts[${index}].id`, artifact.id);
|
|
89
|
+
this.props.formik.setFieldValue(`inputArtifacts[${index}].artifact`, null);
|
|
90
|
+
// Set the account to matchArtifact.artifactAccount if it exists.
|
|
91
|
+
// This account value will be used to determine if the Helm Chart File Path should be displayed.
|
|
92
|
+
if (artifact.matchArtifact) {
|
|
93
|
+
this.props.formik.setFieldValue(`inputArtifacts[${index}].account`, artifact.matchArtifact.artifactAccount);
|
|
94
|
+
} else {
|
|
95
|
+
this.props.formik.setFieldValue(`inputArtifacts[${index}].account`, null);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
private addInputArtifact = () => {
|
|
100
|
+
const stage = this.props.formik.values;
|
|
101
|
+
const newInputArtifacts = [
|
|
102
|
+
...stage.inputArtifacts,
|
|
103
|
+
{
|
|
104
|
+
account: '',
|
|
105
|
+
id: '',
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
this.props.formik.setFieldValue('inputArtifacts', newInputArtifacts);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
private removeInputArtifact = (index: number) => {
|
|
113
|
+
const stage = this.props.formik.values;
|
|
114
|
+
const newInputArtifacts = [...stage.inputArtifacts];
|
|
115
|
+
newInputArtifacts.splice(index, 1);
|
|
116
|
+
this.props.formik.setFieldValue('inputArtifacts', newInputArtifacts);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
private getInputArtifact = (stage: any, index: number) => {
|
|
120
|
+
if (!stage.inputArtifacts || stage.inputArtifacts.length === 0) {
|
|
121
|
+
return {
|
|
122
|
+
account: '',
|
|
123
|
+
id: '',
|
|
124
|
+
};
|
|
125
|
+
} else {
|
|
126
|
+
return stage.inputArtifacts[index];
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
private outputNameChange = (outputName: string) => {
|
|
131
|
+
const stage = this.props.formik.values;
|
|
132
|
+
const expectedArtifacts = stage.expectedArtifacts;
|
|
133
|
+
if (
|
|
134
|
+
expectedArtifacts &&
|
|
135
|
+
expectedArtifacts.length === 1 &&
|
|
136
|
+
expectedArtifacts[0].matchArtifact &&
|
|
137
|
+
expectedArtifacts[0].matchArtifact.type === 'embedded/base64'
|
|
138
|
+
) {
|
|
139
|
+
this.props.formik.setFieldValue('expectedArtifacts', [
|
|
140
|
+
{
|
|
141
|
+
...expectedArtifacts[0],
|
|
142
|
+
matchArtifact: {
|
|
143
|
+
...expectedArtifacts[0].matchArtifact,
|
|
144
|
+
name: outputName,
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
]);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
private overrideChanged = (overrides: any) => {
|
|
152
|
+
this.props.formik.setFieldValue('overrides', overrides);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
public render() {
|
|
156
|
+
const stage = this.props.formik.values;
|
|
157
|
+
return (
|
|
158
|
+
<>
|
|
159
|
+
<h4>Helmfile Options</h4>
|
|
160
|
+
<StageConfigField fieldColumns={3} label={'Name'} helpKey="pipeline.config.bake.manifest.helmfile.name">
|
|
161
|
+
<TextInput
|
|
162
|
+
onChange={(e: React.ChangeEvent<any>) => {
|
|
163
|
+
this.props.formik.setFieldValue('outputName', e.target.value);
|
|
164
|
+
this.outputNameChange(e.target.value);
|
|
165
|
+
}}
|
|
166
|
+
value={stage.outputName}
|
|
167
|
+
/>
|
|
168
|
+
</StageConfigField>
|
|
169
|
+
<h4>Template Artifact</h4>
|
|
170
|
+
<StageArtifactSelectorDelegate
|
|
171
|
+
artifact={this.getInputArtifact(stage, 0).artifact}
|
|
172
|
+
excludedArtifactTypePatterns={BakeHelmfileConfigForm.excludedArtifactTypes}
|
|
173
|
+
expectedArtifactId={this.getInputArtifact(stage, 0).id}
|
|
174
|
+
helpKey="pipeline.config.bake.manifest.expectedArtifact"
|
|
175
|
+
label="Expected Artifact"
|
|
176
|
+
onArtifactEdited={(artifact) => {
|
|
177
|
+
this.onTemplateArtifactEdited(artifact, 0);
|
|
178
|
+
}}
|
|
179
|
+
onExpectedArtifactSelected={(artifact: IExpectedArtifact) => this.onTemplateArtifactSelected(artifact, 0)}
|
|
180
|
+
pipeline={this.props.pipeline}
|
|
181
|
+
stage={stage}
|
|
182
|
+
/>
|
|
183
|
+
{this.state.gitRepoArtifactAccountNames.includes(this.getInputArtifact(stage, 0).account) && (
|
|
184
|
+
<StageConfigField label="Helmfile File Path" helpKey="pipeline.config.bake.manifest.helmfile.filePath">
|
|
185
|
+
<TextInput
|
|
186
|
+
onChange={(e: React.ChangeEvent<any>) => {
|
|
187
|
+
this.props.formik.setFieldValue('helmfileFilePath', e.target.value);
|
|
188
|
+
}}
|
|
189
|
+
value={stage.helmfileFilePath}
|
|
190
|
+
/>
|
|
191
|
+
</StageConfigField>
|
|
192
|
+
)}
|
|
193
|
+
<h4>Overrides</h4>
|
|
194
|
+
{stage.inputArtifacts && stage.inputArtifacts.length > 1 && (
|
|
195
|
+
<div className="row form-group">
|
|
196
|
+
{stage.inputArtifacts.slice(1).map((a: any, index: number) => {
|
|
197
|
+
return (
|
|
198
|
+
<div key={index}>
|
|
199
|
+
<div className="col-md-offset-1 col-md-9">
|
|
200
|
+
<StageArtifactSelectorDelegate
|
|
201
|
+
artifact={a.artifact}
|
|
202
|
+
excludedArtifactTypePatterns={[]}
|
|
203
|
+
expectedArtifactId={a.id}
|
|
204
|
+
label="Expected Artifact"
|
|
205
|
+
onArtifactEdited={(artifact) => {
|
|
206
|
+
this.onTemplateArtifactEdited(artifact, index + 1);
|
|
207
|
+
}}
|
|
208
|
+
onExpectedArtifactSelected={(artifact: IExpectedArtifact) =>
|
|
209
|
+
this.onTemplateArtifactSelected(artifact, index + 1)
|
|
210
|
+
}
|
|
211
|
+
pipeline={this.props.pipeline}
|
|
212
|
+
stage={stage}
|
|
213
|
+
/>
|
|
214
|
+
</div>
|
|
215
|
+
<div className="col-md-1">
|
|
216
|
+
<div className="form-control-static">
|
|
217
|
+
<button onClick={() => this.removeInputArtifact(index + 1)}>
|
|
218
|
+
<span className="glyphicon glyphicon-trash" />
|
|
219
|
+
<span className="sr-only">Remove field</span>
|
|
220
|
+
</button>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
);
|
|
225
|
+
})}
|
|
226
|
+
</div>
|
|
227
|
+
)}
|
|
228
|
+
<StageConfigField fieldColumns={8} label={''}>
|
|
229
|
+
<button className="btn btn-block btn-sm add-new" onClick={() => this.addInputArtifact()}>
|
|
230
|
+
<span className="glyphicon glyphicon-plus-sign" />
|
|
231
|
+
Add value artifact
|
|
232
|
+
</button>
|
|
233
|
+
</StageConfigField>
|
|
234
|
+
<StageConfigField fieldColumns={6} label="Overrides">
|
|
235
|
+
{stage.overrides && (
|
|
236
|
+
<MapEditor
|
|
237
|
+
addButtonLabel={'Add override'}
|
|
238
|
+
model={stage.overrides}
|
|
239
|
+
allowEmpty={true}
|
|
240
|
+
onChange={(o: any) => this.overrideChanged(o)}
|
|
241
|
+
/>
|
|
242
|
+
)}
|
|
243
|
+
</StageConfigField>
|
|
244
|
+
<StageConfigField
|
|
245
|
+
fieldColumns={6}
|
|
246
|
+
helpKey={'pipeline.config.bake.manifest.helm.includeCRDs'}
|
|
247
|
+
label="Include CRDs"
|
|
248
|
+
>
|
|
249
|
+
<CheckboxInput
|
|
250
|
+
value={stage.includeCRDs}
|
|
251
|
+
text={''}
|
|
252
|
+
onChange={() => this.props.formik.setFieldValue('includeCRDs', !stage.includeCRDs)}
|
|
253
|
+
/>
|
|
254
|
+
</StageConfigField>
|
|
255
|
+
<StageConfigField
|
|
256
|
+
fieldColumns={6}
|
|
257
|
+
helpKey={'pipeline.config.bake.manifest.overrideExpressionEvaluation'}
|
|
258
|
+
label="Expression Evaluation"
|
|
259
|
+
>
|
|
260
|
+
<CheckboxInput
|
|
261
|
+
value={stage.evaluateOverrideExpressions}
|
|
262
|
+
text={'Evaluate SpEL expressions in overrides at bake time'}
|
|
263
|
+
onChange={() =>
|
|
264
|
+
this.props.formik.setFieldValue('evaluateOverrideExpressions', !stage.evaluateOverrideExpressions)
|
|
265
|
+
}
|
|
266
|
+
/>
|
|
267
|
+
</StageConfigField>
|
|
268
|
+
</>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { IArtifact, IExecutionContext } from '../../../../../domain';
|
|
2
|
+
import { ARTIFACT_TYPE_EMBEDDED, ARTIFACT_TYPE_REMOTE } from '../../../../../domain';
|
|
3
|
+
|
|
4
|
+
// IArtifact type is wrong and does not represent the real value
|
|
5
|
+
export const getBakedArtifacts = (context: IExecutionContext): Array<IArtifact & { reference: string }> => {
|
|
6
|
+
if ('artifacts' in context) {
|
|
7
|
+
return context.artifacts.filter(
|
|
8
|
+
(a: IArtifact) => (a.type === ARTIFACT_TYPE_EMBEDDED || a.type === ARTIFACT_TYPE_REMOTE) && a.reference,
|
|
9
|
+
);
|
|
10
|
+
} else {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
};
|
|
@@ -11,4 +11,8 @@ export class ArtifactService {
|
|
|
11
11
|
.query({ type: type, artifactName: artifactName })
|
|
12
12
|
.get();
|
|
13
13
|
}
|
|
14
|
+
|
|
15
|
+
public static getArtifactByContentReference(contentRef: string): PromiseLike<{ reference: string }> {
|
|
16
|
+
return REST(`/artifacts/content-address/${contentRef}`).get();
|
|
17
|
+
}
|
|
14
18
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { FormikProps } from 'formik';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import { SETTINGS } from '../../../../config/settings';
|
|
5
|
+
import type { ICDEventsTrigger } from '../../../../domain';
|
|
6
|
+
import { MapEditorInput } from '../../../../forms';
|
|
7
|
+
import { HelpField } from '../../../../help';
|
|
8
|
+
import { FormikFormField, TextInput } from '../../../../presentation';
|
|
9
|
+
|
|
10
|
+
export interface ICDEventsTriggerProps {
|
|
11
|
+
formik: FormikProps<ICDEventsTrigger>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function CDEventsTrigger(cdeventsTriggerProps: ICDEventsTriggerProps) {
|
|
15
|
+
const { formik } = cdeventsTriggerProps;
|
|
16
|
+
const trigger = formik.values;
|
|
17
|
+
const { source, type } = trigger;
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<>
|
|
21
|
+
<FormikFormField
|
|
22
|
+
name="source"
|
|
23
|
+
label="Source"
|
|
24
|
+
help={<HelpField id="pipeline.config.trigger.webhook.source" />}
|
|
25
|
+
input={(props) => (
|
|
26
|
+
<div className="flex-container-v">
|
|
27
|
+
<TextInput {...props} />
|
|
28
|
+
<i>{`${SETTINGS.gateUrl}/webhooks/${type}/${source || '<source>'}`}</i>
|
|
29
|
+
</div>
|
|
30
|
+
)}
|
|
31
|
+
/>
|
|
32
|
+
|
|
33
|
+
<FormikFormField
|
|
34
|
+
name="payloadConstraints"
|
|
35
|
+
label="Payload Constraints"
|
|
36
|
+
help={<HelpField id="pipeline.config.trigger.webhook.payloadConstraints" />}
|
|
37
|
+
input={(props) => <MapEditorInput {...props} addButtonLabel="Add payload constraint" />}
|
|
38
|
+
/>
|
|
39
|
+
|
|
40
|
+
<FormikFormField
|
|
41
|
+
name="attributeConstraints"
|
|
42
|
+
label="Attribute Constraints "
|
|
43
|
+
help={<HelpField id="pipeline.config.trigger.cdevents.attributeConstraints" />}
|
|
44
|
+
input={(props) => <MapEditorInput {...props} addButtonLabel="Add attribute constraint" />}
|
|
45
|
+
/>
|
|
46
|
+
</>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CDEventsTrigger } from './CDEventsTrigger';
|
|
2
|
+
import { ArtifactTypePatterns } from '../../../../artifact';
|
|
3
|
+
import { Registry } from '../../../../registry';
|
|
4
|
+
|
|
5
|
+
Registry.pipeline.registerTrigger({
|
|
6
|
+
component: CDEventsTrigger,
|
|
7
|
+
description: 'Executes the pipeline when a CDEvents webhook is received.',
|
|
8
|
+
excludedArtifactTypePatterns: [ArtifactTypePatterns.JENKINS_FILE],
|
|
9
|
+
key: 'cdevents',
|
|
10
|
+
label: 'CDEvents',
|
|
11
|
+
validators: [
|
|
12
|
+
{
|
|
13
|
+
type: 'serviceAccountAccess',
|
|
14
|
+
message: `You do not have access to the service account configured in this pipeline's CDEvents trigger.
|
|
15
|
+
You will not be able to save your edits to this pipeline.`,
|
|
16
|
+
preventSave: true,
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
});
|
|
@@ -129,7 +129,7 @@ export class CreatePipelineModal extends React.Component<ICreatePipelineModalPro
|
|
|
129
129
|
? this.getDefaultConfig()
|
|
130
130
|
: command.config;
|
|
131
131
|
|
|
132
|
-
pipelineConfig.name = command.name;
|
|
132
|
+
pipelineConfig.name = command.name.trim();
|
|
133
133
|
pipelineConfig.index = this.props.application.getDataSource('pipelineConfigs').data.length;
|
|
134
134
|
delete pipelineConfig.id;
|
|
135
135
|
|