@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.
Files changed (52) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/application/config/defaultTagFilter/DefaultTagFilterConfig.d.ts +27 -0
  3. package/dist/application/config/defaultTagFilter/defaultTagFilterConfig.component.d.ts +1 -0
  4. package/dist/config/settings.d.ts +1 -0
  5. package/dist/domain/IArtifact.d.ts +2 -0
  6. package/dist/domain/ITrigger.d.ts +5 -0
  7. package/dist/index.js +36 -36
  8. package/dist/index.js.map +1 -1
  9. package/dist/manifest/ManifestYaml.d.ts +10 -4
  10. package/dist/pipeline/config/stages/bakeManifest/ManifestRenderers.d.ts +2 -0
  11. package/dist/pipeline/config/stages/bakeManifest/helmfile/BakeHelmfileConfigForm.d.ts +18 -0
  12. package/dist/pipeline/config/stages/bakeManifest/utils/getBakedArtifacts.d.ts +4 -0
  13. package/dist/pipeline/config/stages/bakeManifest/utils/getContentReference.d.ts +1 -0
  14. package/dist/pipeline/config/triggers/artifacts/ArtifactService.d.ts +3 -0
  15. package/dist/pipeline/config/triggers/cdevents/CDEventsTrigger.d.ts +7 -0
  16. package/dist/pipeline/config/triggers/cdevents/cdevents.trigger.d.ts +1 -0
  17. package/dist/pipeline/config/triggers/index.d.ts +1 -0
  18. package/dist/pipeline/executions/Executions.d.ts +1 -0
  19. package/dist/pipeline/index.d.ts +2 -0
  20. package/dist/presentation/forms/inputs/NumberConcurrencyInput.d.ts +7 -0
  21. package/package.json +2 -2
  22. package/src/application/config/applicationConfig.controller.js +26 -0
  23. package/src/application/config/applicationConfig.view.html +9 -0
  24. package/src/application/config/defaultTagFilter/DefaultTagFilterConfig.spec.tsx +75 -0
  25. package/src/application/config/defaultTagFilter/DefaultTagFilterConfig.tsx +161 -0
  26. package/src/application/config/defaultTagFilter/defaultTagFilterConfig.component.ts +15 -0
  27. package/src/application/config/defaultTagFilter/defaultTagFilterConfig.less +8 -0
  28. package/src/artifact/ArtifactIconService.ts +1 -0
  29. package/src/artifact/ArtifactTypes.ts +1 -0
  30. package/src/config/settings.ts +1 -0
  31. package/src/domain/IArtifact.ts +3 -0
  32. package/src/domain/ITrigger.ts +4 -0
  33. package/src/help/help.contents.ts +10 -0
  34. package/src/manifest/ManifestYaml.tsx +29 -7
  35. package/src/pipeline/config/stages/bakeManifest/BakeManifestConfig.tsx +4 -1
  36. package/src/pipeline/config/stages/bakeManifest/BakeManifestDetailsTab.tsx +24 -12
  37. package/src/pipeline/config/stages/bakeManifest/BakeManifestStageForm.tsx +9 -2
  38. package/src/pipeline/config/stages/bakeManifest/ManifestRenderers.ts +2 -0
  39. package/src/pipeline/config/stages/bakeManifest/helmfile/BakeHelmfileConfigForm.spec.tsx +132 -0
  40. package/src/pipeline/config/stages/bakeManifest/helmfile/BakeHelmfileConfigForm.tsx +271 -0
  41. package/src/pipeline/config/stages/bakeManifest/utils/getBakedArtifacts.ts +13 -0
  42. package/src/pipeline/config/stages/bakeManifest/utils/getContentReference.ts +3 -0
  43. package/src/pipeline/config/triggers/artifacts/ArtifactService.ts +4 -0
  44. package/src/pipeline/config/triggers/cdevents/CDEventsTrigger.tsx +48 -0
  45. package/src/pipeline/config/triggers/cdevents/cdevents.trigger.ts +19 -0
  46. package/src/pipeline/config/triggers/index.ts +1 -0
  47. package/src/pipeline/create/CreatePipelineModal.tsx +1 -1
  48. package/src/pipeline/details/StageFailureMessage.tsx +10 -24
  49. package/src/pipeline/executions/Executions.tsx +13 -0
  50. package/src/pipeline/index.ts +2 -0
  51. package/src/pipeline/status/ExecutionCancellationReason.tsx +1 -1
  52. 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 type { IArtifact } from '../../../../domain';
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: IArtifact[] = (stage.context.artifacts || []).filter(
18
- (a: IArtifact) => a.type === 'embedded/base64',
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
- <ManifestYaml
25
- key={i}
26
- linkName={bakedArtifacts.length > 1 ? `Baked Manifest ${i} YAML` : 'Baked Manifest YAML'}
27
- manifestText={decodeUnicodeBase64(artifact.reference)}
28
- modalTitle="Baked Manifest"
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
+ };
@@ -0,0 +1,3 @@
1
+ export const getContentReference = (uri: string): string => {
2
+ return uri.replace(/^ref?:\/\//, '');
3
+ };
@@ -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
+ });
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  import './artifactory/artifactory.trigger';
4
+ import './cdevents/cdevents.trigger';
4
5
  import './concourse/concourse.trigger';
5
6
  import './cron/cron.trigger';
6
7
  import './git/git.trigger';
@@ -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