@spinnaker/cloudrun 0.1.6

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 (116) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/LICENSE.txt +203 -0
  3. package/dist/cloudrun.module.d.ts +4 -0
  4. package/dist/cloudrun.settings.d.ts +7 -0
  5. package/dist/common/cloudrunHealth.d.ts +3 -0
  6. package/dist/common/componentUrlDetails.component.d.ts +1 -0
  7. package/dist/common/conditionalDescriptionListItem.component.d.ts +1 -0
  8. package/dist/common/domain/ICloudrunInstance.d.ts +20 -0
  9. package/dist/common/domain/ICloudrunLoadBalancer.d.ts +18 -0
  10. package/dist/common/domain/index.d.ts +2 -0
  11. package/dist/common/loadBalancerMessage.component.d.ts +1 -0
  12. package/dist/help/cloudrun.help.d.ts +1 -0
  13. package/dist/index.d.ts +3 -0
  14. package/dist/index.js +2 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/instance/details/details.controller.d.ts +1 -0
  17. package/dist/interfaces/index.d.ts +1 -0
  18. package/dist/interfaces/infrastructure.types.d.ts +18 -0
  19. package/dist/loadBalancer/configure/wizard/allocationConfigurationRow.component.d.ts +1 -0
  20. package/dist/loadBalancer/configure/wizard/basicSettings.component.d.ts +1 -0
  21. package/dist/loadBalancer/configure/wizard/stageAllocationConfigurationRow.component.d.ts +1 -0
  22. package/dist/loadBalancer/configure/wizard/wizard.controller.d.ts +2 -0
  23. package/dist/loadBalancer/details/details.controller.d.ts +1 -0
  24. package/dist/loadBalancer/index.d.ts +3 -0
  25. package/dist/loadBalancer/loadBalancerTransformer.d.ts +38 -0
  26. package/dist/manifest/ManifestSource.d.ts +4 -0
  27. package/dist/manifest/index.d.ts +2 -0
  28. package/dist/manifest/manifest.service.d.ts +25 -0
  29. package/dist/manifest/manifestCommandBuilder.service.d.ts +30 -0
  30. package/dist/manifest/wizard/BasicSettings.d.ts +17 -0
  31. package/dist/pipeline/pipeline.module.d.ts +1 -0
  32. package/dist/pipeline/stages/deployManifest/DeployStageConfig.d.ts +15 -0
  33. package/dist/pipeline/stages/deployManifest/DeployStageForm.d.ts +23 -0
  34. package/dist/pipeline/stages/deployManifest/ManifestBindArtifactsSelector.d.ts +17 -0
  35. package/dist/pipeline/stages/deployManifest/deploy.validator.d.ts +2 -0
  36. package/dist/pipeline/stages/deployManifest/deployStage.d.ts +1 -0
  37. package/dist/pipeline/stages/deployManifest/manifestStatus/DeployStatus.d.ts +22 -0
  38. package/dist/pipeline/stages/deployManifest/manifestStatus/DeployStatusPills.d.ts +8 -0
  39. package/dist/pipeline/stages/deployManifest/manifestStatus/ManifestDetailsLink.d.ts +20 -0
  40. package/dist/pipeline/stages/deployManifest/manifestStatus/ManifestEvents.d.ts +9 -0
  41. package/dist/pipeline/stages/deployManifest/manifestStatus/ManifestStatus.d.ts +8 -0
  42. package/dist/pipeline/stages/deployManifest/serverGroupNamePreview.d.ts +9 -0
  43. package/dist/pipeline/stages/editLoadBalancer/cloudrunEditLoadBalancerStage.d.ts +1 -0
  44. package/dist/pipeline/stages/editLoadBalancer/loadBalancerChoice.modal.controller.d.ts +1 -0
  45. package/dist/rolloutStrategy/redblack.strategy.d.ts +2 -0
  46. package/dist/serverGroup/configure/serverGroupCommandBuilder.service.d.ts +74 -0
  47. package/dist/serverGroup/configure/wizard/BasicSettings.d.ts +29 -0
  48. package/dist/serverGroup/configure/wizard/ConfigFiles.d.ts +15 -0
  49. package/dist/serverGroup/configure/wizard/serverGroupWizard.d.ts +25 -0
  50. package/dist/serverGroup/details/details.controller.d.ts +1 -0
  51. package/dist/serverGroup/index.d.ts +3 -0
  52. package/dist/serverGroup/serverGroupTransformer.service.d.ts +31 -0
  53. package/package.json +57 -0
  54. package/src/cloudrun.module.ts +73 -0
  55. package/src/cloudrun.settings.ts +14 -0
  56. package/src/common/cloudrunHealth.ts +3 -0
  57. package/src/common/componentUrlDetails.component.ts +22 -0
  58. package/src/common/conditionalDescriptionListItem.component.ts +37 -0
  59. package/src/common/domain/ICloudrunInstance.ts +21 -0
  60. package/src/common/domain/ICloudrunLoadBalancer.ts +18 -0
  61. package/src/common/domain/index.ts +2 -0
  62. package/src/common/loadBalancerMessage.component.html +11 -0
  63. package/src/common/loadBalancerMessage.component.ts +13 -0
  64. package/src/help/cloudrun.help.ts +99 -0
  65. package/src/index.ts +3 -0
  66. package/src/instance/details/details.controller.ts +84 -0
  67. package/src/instance/details/details.html +68 -0
  68. package/src/interfaces/index.ts +1 -0
  69. package/src/interfaces/infrastructure.types.ts +23 -0
  70. package/src/loadBalancer/configure/wizard/allocationConfigurationRow.component.ts +70 -0
  71. package/src/loadBalancer/configure/wizard/basicSettings.component.html +44 -0
  72. package/src/loadBalancer/configure/wizard/basicSettings.component.ts +86 -0
  73. package/src/loadBalancer/configure/wizard/stageAllocationConfigurationRow.component.html +45 -0
  74. package/src/loadBalancer/configure/wizard/stageAllocationConfigurationRow.component.ts +84 -0
  75. package/src/loadBalancer/configure/wizard/wizard.controller.ts +157 -0
  76. package/src/loadBalancer/configure/wizard/wizard.html +39 -0
  77. package/src/loadBalancer/configure/wizard/wizard.less +9 -0
  78. package/src/loadBalancer/details/details.controller.ts +140 -0
  79. package/src/loadBalancer/details/details.html +89 -0
  80. package/src/loadBalancer/index.ts +3 -0
  81. package/src/loadBalancer/loadBalancerTransformer.ts +168 -0
  82. package/src/logo/cloudrun.icon.svg +27 -0
  83. package/src/logo/cloudrun.logo.less +6 -0
  84. package/src/logo/cloudrun.logo.png +0 -0
  85. package/src/manifest/ManifestSource.ts +4 -0
  86. package/src/manifest/index.ts +2 -0
  87. package/src/manifest/manifest.service.ts +89 -0
  88. package/src/manifest/manifestCommandBuilder.service.ts +116 -0
  89. package/src/manifest/wizard/BasicSettings.tsx +57 -0
  90. package/src/pipeline/pipeline.module.ts +6 -0
  91. package/src/pipeline/stages/deployManifest/DeployStageConfig.tsx +73 -0
  92. package/src/pipeline/stages/deployManifest/DeployStageForm.tsx +185 -0
  93. package/src/pipeline/stages/deployManifest/ManifestBindArtifactsSelector.tsx +69 -0
  94. package/src/pipeline/stages/deployManifest/deploy.validator.ts +38 -0
  95. package/src/pipeline/stages/deployManifest/deployStage.ts +28 -0
  96. package/src/pipeline/stages/deployManifest/manifestStatus/DeployStatus.tsx +108 -0
  97. package/src/pipeline/stages/deployManifest/manifestStatus/DeployStatusPills.tsx +47 -0
  98. package/src/pipeline/stages/deployManifest/manifestStatus/ManifestDetailsLink.tsx +92 -0
  99. package/src/pipeline/stages/deployManifest/manifestStatus/ManifestEvents.tsx +87 -0
  100. package/src/pipeline/stages/deployManifest/manifestStatus/ManifestStatus.less +24 -0
  101. package/src/pipeline/stages/deployManifest/manifestStatus/ManifestStatus.tsx +46 -0
  102. package/src/pipeline/stages/deployManifest/serverGroupNamePreview.tsx +28 -0
  103. package/src/pipeline/stages/editLoadBalancer/cloudrunEditLoadBalancerStage.ts +74 -0
  104. package/src/pipeline/stages/editLoadBalancer/editLoadBalancerExecutionDetails.html +33 -0
  105. package/src/pipeline/stages/editLoadBalancer/editLoadBalancerStage.html +51 -0
  106. package/src/pipeline/stages/editLoadBalancer/loadBalancerChoice.modal.controller.ts +65 -0
  107. package/src/pipeline/stages/editLoadBalancer/loadBalancerChoice.modal.html +53 -0
  108. package/src/rolloutStrategy/redblack.strategy.ts +7 -0
  109. package/src/serverGroup/configure/serverGroupCommandBuilder.service.ts +254 -0
  110. package/src/serverGroup/configure/wizard/BasicSettings.tsx +160 -0
  111. package/src/serverGroup/configure/wizard/ConfigFiles.tsx +75 -0
  112. package/src/serverGroup/configure/wizard/serverGroupWizard.tsx +150 -0
  113. package/src/serverGroup/details/details.controller.ts +134 -0
  114. package/src/serverGroup/details/details.html +94 -0
  115. package/src/serverGroup/index.ts +3 -0
  116. package/src/serverGroup/serverGroupTransformer.service.ts +61 -0
@@ -0,0 +1,89 @@
1
+ import type { Application, IManifest } from '@spinnaker/core';
2
+ import { ManifestReader } from '@spinnaker/core';
3
+
4
+ export interface IStageManifest {
5
+ kind: string;
6
+ apiVersion: string;
7
+ metadata: {
8
+ namespace: string;
9
+ name: string;
10
+ };
11
+ }
12
+
13
+ export interface IManifestParams {
14
+ account: string;
15
+ location: string;
16
+ name: string;
17
+ }
18
+
19
+ export type IManifestCallback = (manifest: IManifest) => void;
20
+
21
+ export class CloudrunManifestService {
22
+ public static subscribe(app: Application, params: IManifestParams, fn: IManifestCallback): () => void {
23
+ CloudrunManifestService.updateManifest(params, fn);
24
+ return app.onRefresh(null, () => CloudrunManifestService.updateManifest(params, fn));
25
+ }
26
+
27
+ private static updateManifest(params: IManifestParams, fn: IManifestCallback) {
28
+ ManifestReader.getManifest(params.account, params.location, params.name).then((manifest) => fn(manifest));
29
+ }
30
+
31
+ public static manifestIdentifier(manifest: IStageManifest) {
32
+ const kind = manifest.kind.toLowerCase();
33
+ // manifest.metadata.namespace doesn't exist if it's a namespace being deployed
34
+ const namespace = (manifest.metadata.namespace || '_').toLowerCase();
35
+ const name = manifest.metadata.name.toLowerCase();
36
+ const apiVersion = (manifest.apiVersion || '_').toLowerCase();
37
+ // assuming this identifier is opaque and not parsed anywhere. Including the
38
+ // apiVersion will prevent collisions with CRD kinds without having any visible
39
+ // effect elsewhere
40
+ return `${namespace} ${kind} ${apiVersion} ${name}`;
41
+ }
42
+
43
+ public static stageManifestToManifestParams(manifest: IStageManifest, account: string): IManifestParams {
44
+ return {
45
+ account,
46
+ name: CloudrunManifestService.scopedKind(manifest) + ' ' + manifest.metadata.name,
47
+ location: manifest.metadata.namespace == null ? '_' : manifest.metadata.namespace,
48
+ };
49
+ }
50
+
51
+ private static apiGroup(manifest: IStageManifest): string {
52
+ const parts = (manifest.apiVersion || '_').split('/');
53
+ if (parts.length < 2) {
54
+ return '';
55
+ }
56
+ return parts[0];
57
+ }
58
+
59
+ private static isCRDGroup(manifest: IStageManifest): boolean {
60
+ return !CloudrunManifestService.BUILT_IN_GROUPS.includes(CloudrunManifestService.apiGroup(manifest));
61
+ }
62
+
63
+ private static scopedKind(manifest: IStageManifest): string {
64
+ if (CloudrunManifestService.isCRDGroup(manifest)) {
65
+ return manifest.kind + '.' + CloudrunManifestService.apiGroup(manifest);
66
+ }
67
+
68
+ return manifest.kind;
69
+ }
70
+
71
+ private static readonly BUILT_IN_GROUPS = [
72
+ '',
73
+ 'core',
74
+ 'batch',
75
+ 'apps',
76
+ 'extensions',
77
+ 'storage.k8s.io',
78
+ 'apiextensions.k8s.io',
79
+ 'apiregistration.k8s.io',
80
+ 'policy',
81
+ 'scheduling.k8s.io',
82
+ 'settings.k8s.io',
83
+ 'authorization.k8s.io',
84
+ 'authentication.k8s.io',
85
+ 'rbac.authorization.k8s.io',
86
+ 'certifcates.k8s.io',
87
+ 'networking.k8s.io',
88
+ ];
89
+ }
@@ -0,0 +1,116 @@
1
+ import { cloneDeep } from 'lodash';
2
+ import { $q } from 'ngimport';
3
+
4
+ import type { Application, IAccountDetails, IArtifactAccount, IMoniker } from '@spinnaker/core';
5
+ import { AccountService } from '@spinnaker/core';
6
+
7
+ import { ManifestSource } from './ManifestSource';
8
+
9
+ export interface ICloudrunManifestCommandData {
10
+ command: ICloudrunManifestCommand;
11
+ metadata: ICloudrunManifestCommandMetadata;
12
+ }
13
+
14
+ export interface ICloudrunManifestCommand {
15
+ account: string;
16
+ cloudProvider: string;
17
+ manifest: any;
18
+ manifests: any[];
19
+ relationships: ICloudrunManifestSpinnakerRelationships;
20
+ moniker: IMoniker;
21
+ manifestArtifactId?: string;
22
+ manifestArtifactAccount?: string;
23
+ source: ManifestSource;
24
+ versioned?: boolean;
25
+ }
26
+
27
+ export interface ICloudrunManifestCommandMetadata {
28
+ backingData: any;
29
+ }
30
+
31
+ export interface ICloudrunManifestSpinnakerRelationships {
32
+ loadBalancers?: string[];
33
+ securityGroups?: string[];
34
+ }
35
+
36
+ export class CloudrunManifestCommandBuilder {
37
+ public static manifestCommandIsValid(command: ICloudrunManifestCommand): boolean {
38
+ if (!command.moniker) {
39
+ return false;
40
+ }
41
+
42
+ if (!command.moniker.app) {
43
+ return false;
44
+ }
45
+
46
+ return true;
47
+ }
48
+
49
+ public static copyAndCleanCommand(input: ICloudrunManifestCommand): ICloudrunManifestCommand {
50
+ const command = cloneDeep(input);
51
+ return command;
52
+ }
53
+
54
+ public static buildNewManifestCommand(
55
+ app: Application,
56
+ sourceManifest?: any,
57
+ sourceMoniker?: IMoniker,
58
+ sourceAccount?: string,
59
+ ): PromiseLike<ICloudrunManifestCommandData> {
60
+ const dataToFetch = {
61
+ accounts: AccountService.getAllAccountDetailsForProvider('cloudrun'),
62
+ artifactAccounts: AccountService.getArtifactAccounts(),
63
+ };
64
+
65
+ return $q
66
+ .all(dataToFetch)
67
+ .then((backingData: { accounts: IAccountDetails[]; artifactAccounts: IArtifactAccount[] }) => {
68
+ const { accounts, artifactAccounts } = backingData;
69
+
70
+ const account = accounts.some((a) => a.name === sourceAccount)
71
+ ? accounts.find((a) => a.name === sourceAccount).name
72
+ : accounts.length
73
+ ? accounts[0].name
74
+ : null;
75
+
76
+ let manifestArtifactAccount: string = null;
77
+ const [artifactAccountData] = artifactAccounts;
78
+ if (artifactAccountData) {
79
+ manifestArtifactAccount = artifactAccountData.name;
80
+ }
81
+
82
+ const cloudProvider = 'cloudrun';
83
+ const moniker = sourceMoniker || {
84
+ app: app.name,
85
+ };
86
+
87
+ const relationships = {
88
+ loadBalancers: [] as string[],
89
+ securityGroups: [] as string[],
90
+ };
91
+
92
+ const versioned: any = null;
93
+
94
+ return {
95
+ command: {
96
+ cloudProvider,
97
+ manifest: null,
98
+ manifests: Array.isArray(sourceManifest)
99
+ ? sourceManifest
100
+ : sourceManifest != null
101
+ ? [sourceManifest]
102
+ : null,
103
+ relationships,
104
+ moniker,
105
+ account,
106
+ versioned,
107
+ manifestArtifactAccount,
108
+ source: ManifestSource.TEXT,
109
+ },
110
+ metadata: {
111
+ backingData,
112
+ },
113
+ } as ICloudrunManifestCommandData;
114
+ });
115
+ }
116
+ }
@@ -0,0 +1,57 @@
1
+ import type { FormikProps } from 'formik';
2
+ import React from 'react';
3
+
4
+ import type { IAccount } from '@spinnaker/core';
5
+ import { AccountSelectInput, HelpField } from '@spinnaker/core';
6
+
7
+ import type { ICloudrunManifestCommandData } from '../manifestCommandBuilder.service';
8
+
9
+ export interface IManifestBasicSettingsProps {
10
+ accounts: IAccount[];
11
+ onAccountSelect: (account: string) => void;
12
+ selectedAccount: string;
13
+ }
14
+
15
+ export function ManifestBasicSettings({ accounts, onAccountSelect, selectedAccount }: IManifestBasicSettingsProps) {
16
+ return (
17
+ <div className="form-horizontal">
18
+ <div className="form-group">
19
+ <div className="col-md-3 sm-label-right">
20
+ Account <HelpField id="cloudrun.manifest.account" />
21
+ </div>
22
+ <div className="col-md-8">
23
+ <AccountSelectInput
24
+ value={selectedAccount}
25
+ onChange={(evt: any) => onAccountSelect(evt.target.value)}
26
+ readOnly={false}
27
+ accounts={accounts}
28
+ provider="cloudrun"
29
+ />
30
+ </div>
31
+ </div>
32
+ </div>
33
+ );
34
+ }
35
+
36
+ export interface IWizardManifestBasicSettingsProps {
37
+ formik: FormikProps<ICloudrunManifestCommandData>;
38
+ }
39
+
40
+ export class WizardManifestBasicSettings extends React.Component<IWizardManifestBasicSettingsProps> {
41
+ private accountUpdated = (account: string): void => {
42
+ const { formik } = this.props;
43
+ formik.values.command.account = account;
44
+ formik.setFieldValue('account', account);
45
+ };
46
+
47
+ public render() {
48
+ const { formik } = this.props;
49
+ return (
50
+ <ManifestBasicSettings
51
+ accounts={formik.values.metadata.backingData.accounts}
52
+ onAccountSelect={this.accountUpdated}
53
+ selectedAccount={formik.values.command.account}
54
+ />
55
+ );
56
+ }
57
+ }
@@ -0,0 +1,6 @@
1
+ import { module } from 'angular';
2
+
3
+ import { CLOUDRUN_EDIT_LOAD_BALANCER_STAGE } from './stages/editLoadBalancer/cloudrunEditLoadBalancerStage';
4
+
5
+ export const CLOUDRUN_PIPELINE_MODULE = 'spinnaker.cloudrun.pipeline.module';
6
+ module(CLOUDRUN_PIPELINE_MODULE, [CLOUDRUN_EDIT_LOAD_BALANCER_STAGE]);
@@ -0,0 +1,73 @@
1
+ import { cloneDeep } from 'lodash';
2
+ import React from 'react';
3
+ import { from as observableFrom, Subject } from 'rxjs';
4
+ import { takeUntil } from 'rxjs/operators';
5
+
6
+ import type { IAccountDetails, IStage, IStageConfigProps } from '@spinnaker/core';
7
+ import { AccountService, FormikStageConfig } from '@spinnaker/core';
8
+
9
+ import { DeployStageForm } from './DeployStageForm';
10
+ import { ManifestSource } from '../../../manifest/ManifestSource';
11
+
12
+ interface IDeployManifestStageConfigState {
13
+ accounts: IAccountDetails[];
14
+ }
15
+
16
+ export class DeployStageConfig extends React.Component<IStageConfigProps, IDeployManifestStageConfigState> {
17
+ private stage: IStage;
18
+ private destroy$ = new Subject();
19
+
20
+ public constructor(props: IStageConfigProps) {
21
+ super(props);
22
+ this.state = {
23
+ accounts: [],
24
+ };
25
+ const { stage: initialStageConfig } = props;
26
+ const stage = cloneDeep(initialStageConfig);
27
+ if (!stage.source) {
28
+ stage.source = ManifestSource.TEXT;
29
+ }
30
+ if (!stage.skipExpressionEvaluation) {
31
+ stage.skipExpressionEvaluation = false;
32
+ }
33
+ if (!stage.cloudProvider) {
34
+ stage.cloudProvider = 'cloudrun';
35
+ }
36
+ if (!stage.moniker) {
37
+ stage.moniker = {};
38
+ }
39
+ if (!stage.moniker.app) {
40
+ stage.moniker.app = props.application.name;
41
+ }
42
+ // Intentionally initializing the stage config only once in the constructor
43
+ // The stage config is then completely owned within FormikStageConfig's Formik state
44
+ this.stage = stage;
45
+ }
46
+
47
+ public componentDidMount() {
48
+ this.fetchAccounts();
49
+ }
50
+
51
+ private fetchAccounts = (): void => {
52
+ observableFrom(AccountService.getAllAccountDetailsForProvider('cloudrun'))
53
+ .pipe(takeUntil(this.destroy$))
54
+ .subscribe((accounts: IAccountDetails[]) => {
55
+ this.setState({ accounts });
56
+ });
57
+ };
58
+
59
+ public componentWillUnmount() {
60
+ this.destroy$.next();
61
+ }
62
+
63
+ public render() {
64
+ return (
65
+ <FormikStageConfig
66
+ {...this.props}
67
+ stage={this.stage}
68
+ onChange={this.props.updateStage}
69
+ render={(props) => <DeployStageForm {...props} accounts={this.state.accounts} />}
70
+ />
71
+ );
72
+ }
73
+ }
@@ -0,0 +1,185 @@
1
+ import { capitalize, get, isEmpty, map } from 'lodash';
2
+ import React from 'react';
3
+ import type { Option } from 'react-select';
4
+
5
+ import type { IAccountDetails, IArtifact, IExpectedArtifact, IFormikStageConfigInjectedProps } from '@spinnaker/core';
6
+ import {
7
+ ArtifactTypePatterns,
8
+ CheckboxInput,
9
+ RadioButtonInput,
10
+ StageArtifactSelectorDelegate,
11
+ StageConfigField,
12
+ yamlDocumentsToString,
13
+ YamlEditor,
14
+ } from '@spinnaker/core';
15
+
16
+ import type { IManifestBindArtifact } from './ManifestBindArtifactsSelector';
17
+ import { ManifestBindArtifactsSelector } from './ManifestBindArtifactsSelector';
18
+ import { ManifestSource } from '../../../manifest/ManifestSource';
19
+ import { ManifestBasicSettings } from '../../../manifest/wizard/BasicSettings';
20
+ import { ServerGroupNamePreview } from './serverGroupNamePreview';
21
+
22
+ interface IDeployManifestStageConfigFormProps {
23
+ accounts: IAccountDetails[];
24
+ }
25
+
26
+ interface IDeployManifestStageConfigFormState {
27
+ rawManifest: string;
28
+ application: string;
29
+ stack: string;
30
+ details: string;
31
+ }
32
+
33
+ export class DeployStageForm extends React.Component<
34
+ IDeployManifestStageConfigFormProps & IFormikStageConfigInjectedProps,
35
+ IDeployManifestStageConfigFormState
36
+ > {
37
+ private readonly excludedManifestArtifactTypes = [
38
+ ArtifactTypePatterns.FRONT50_PIPELINE_TEMPLATE,
39
+ ArtifactTypePatterns.MAVEN_FILE,
40
+ ];
41
+
42
+ public constructor(props: IDeployManifestStageConfigFormProps & IFormikStageConfigInjectedProps) {
43
+ super(props);
44
+ const stage = this.props.formik.values;
45
+ const manifests: any[] = get(props.formik.values, 'manifests');
46
+ const isTextManifest: boolean = get(props.formik.values, 'source') === ManifestSource.TEXT;
47
+ this.state = {
48
+ rawManifest: !isEmpty(manifests) && isTextManifest ? yamlDocumentsToString(manifests) : '',
49
+ application: this.props.application.name,
50
+ stack: get(stage, 'stack'),
51
+ details: get(stage, 'details'),
52
+ };
53
+ }
54
+
55
+ private getSourceOptions = (): Array<Option<string>> => {
56
+ return map([ManifestSource.TEXT, ManifestSource.ARTIFACT], (option) => ({
57
+ label: capitalize(option),
58
+ value: option,
59
+ }));
60
+ };
61
+
62
+ private handleRawManifestChange = (rawManifest: string, manifests: any): void => {
63
+ this.setState({
64
+ rawManifest,
65
+ });
66
+ this.props.formik.setFieldValue('manifests', manifests);
67
+ };
68
+
69
+ private onManifestArtifactSelected = (expectedArtifactId: string): void => {
70
+ this.props.formik.setFieldValue('manifestArtifactId', expectedArtifactId);
71
+ this.props.formik.setFieldValue('manifestArtifact', null);
72
+ };
73
+
74
+ private onManifestArtifactEdited = (artifact: IArtifact) => {
75
+ this.props.formik.setFieldValue('manifestArtifactId', null);
76
+ this.props.formik.setFieldValue('manifestArtifact', artifact);
77
+ };
78
+
79
+ private getRequiredArtifacts = (): IManifestBindArtifact[] => {
80
+ const { requiredArtifactIds, requiredArtifacts } = this.props.formik.values;
81
+ return (requiredArtifactIds || [])
82
+ .map((id: string) => ({ expectedArtifactId: id }))
83
+ .concat(requiredArtifacts || []);
84
+ };
85
+
86
+ private onRequiredArtifactsChanged = (bindings: IManifestBindArtifact[]): void => {
87
+ this.props.formik.setFieldValue(
88
+ 'requiredArtifactIds',
89
+ bindings.filter((b) => b.expectedArtifactId).map((b) => b.expectedArtifactId),
90
+ );
91
+ this.props.formik.setFieldValue(
92
+ 'requiredArtifacts',
93
+ bindings.filter((b) => b.artifact),
94
+ );
95
+ };
96
+
97
+ public render() {
98
+ const stage = this.props.formik.values;
99
+ return (
100
+ <div className="form-horizontal">
101
+ <h4>Basic Settings</h4>
102
+ <ManifestBasicSettings
103
+ accounts={this.props.accounts}
104
+ onAccountSelect={(accountName) => this.props.formik.setFieldValue('account', accountName)}
105
+ selectedAccount={stage.account}
106
+ />
107
+ <StageConfigField label="Application">
108
+ <input
109
+ className="form-control input-sm"
110
+ type="text"
111
+ value={this.props.application.name}
112
+ onChange={(event) => this.props.formik.setFieldValue('application', event.target.value)}
113
+ disabled
114
+ />
115
+ </StageConfigField>
116
+ <StageConfigField label="Stack">
117
+ <input
118
+ className="form-control input-sm"
119
+ type="text"
120
+ value={stage.stack}
121
+ onChange={(event) => this.props.formik.setFieldValue('stack', event.target.value)}
122
+ />
123
+ </StageConfigField>
124
+ <StageConfigField label="Details">
125
+ <input
126
+ className="form-control input-sm"
127
+ type="text"
128
+ value={stage.details}
129
+ onChange={(event) => this.props.formik.setFieldValue('details', event.target.value)}
130
+ />
131
+ </StageConfigField>
132
+ <ServerGroupNamePreview
133
+ application={this.props.application.name}
134
+ stack={stage.stack}
135
+ details={stage.details}
136
+ ></ServerGroupNamePreview>
137
+
138
+ <hr />
139
+ <h4>Manifest Configuration</h4>
140
+ <StageConfigField label="Manifest Source" helpKey="cloudrun.manifest.source">
141
+ <RadioButtonInput
142
+ options={this.getSourceOptions()}
143
+ onChange={(e: any) => this.props.formik.setFieldValue('source', e.target.value)}
144
+ value={stage.source}
145
+ />
146
+ </StageConfigField>
147
+ {stage.source === ManifestSource.TEXT && (
148
+ <StageConfigField label="Manifest">
149
+ <YamlEditor onChange={this.handleRawManifestChange} value={this.state.rawManifest} />
150
+ </StageConfigField>
151
+ )}
152
+ {stage.source === ManifestSource.ARTIFACT && (
153
+ <>
154
+ <StageArtifactSelectorDelegate
155
+ artifact={stage.manifestArtifact}
156
+ excludedArtifactTypePatterns={this.excludedManifestArtifactTypes}
157
+ expectedArtifactId={stage.manifestArtifactId}
158
+ helpKey="cloudrun.manifest.expectedArtifact"
159
+ label="Manifest Artifact"
160
+ onArtifactEdited={this.onManifestArtifactEdited}
161
+ onExpectedArtifactSelected={(artifact: IExpectedArtifact) => this.onManifestArtifactSelected(artifact.id)}
162
+ pipeline={this.props.pipeline}
163
+ stage={stage}
164
+ />
165
+ <StageConfigField label="Expression Evaluation" helpKey="cloudrun.manifest.skipExpressionEvaluation">
166
+ <CheckboxInput
167
+ checked={stage.skipExpressionEvaluation === true}
168
+ onChange={(e: any) => this.props.formik.setFieldValue('skipExpressionEvaluation', e.target.checked)}
169
+ text="Skip SpEL expression evaluation"
170
+ />
171
+ </StageConfigField>
172
+ </>
173
+ )}
174
+ <StageConfigField label="Required Artifacts to Bind" helpKey="cloudrun.manifest.requiredArtifactsToBind">
175
+ <ManifestBindArtifactsSelector
176
+ bindings={this.getRequiredArtifacts()}
177
+ onChangeBindings={this.onRequiredArtifactsChanged}
178
+ pipeline={this.props.pipeline}
179
+ stage={stage}
180
+ />
181
+ </StageConfigField>
182
+ </div>
183
+ );
184
+ }
185
+ }
@@ -0,0 +1,69 @@
1
+ import React from 'react';
2
+
3
+ import type { IArtifact, IExpectedArtifact, IPipeline, IStage } from '@spinnaker/core';
4
+ import { ArtifactTypePatterns, StageArtifactSelector } from '@spinnaker/core';
5
+
6
+ export interface IManifestBindArtifact {
7
+ expectedArtifactId?: string;
8
+ artifact?: IArtifact;
9
+ }
10
+
11
+ export interface IManifestBindArtifactsSelectorProps {
12
+ pipeline: IPipeline;
13
+ stage: IStage;
14
+ bindings?: IManifestBindArtifact[];
15
+ onChangeBindings: (_: IManifestBindArtifact[]) => void;
16
+ }
17
+
18
+ export class ManifestBindArtifactsSelector extends React.Component<IManifestBindArtifactsSelectorProps> {
19
+ private onChangeBinding = (index: number, binding: IManifestBindArtifact) => {
20
+ const bindings = (this.props.bindings || []).slice(0);
21
+ bindings[index] = binding;
22
+ this.props.onChangeBindings(bindings);
23
+ };
24
+
25
+ private onRemoveBinding = (index: number) => {
26
+ const bindings = (this.props.bindings || []).slice(0);
27
+ bindings.splice(index, 1);
28
+ this.props.onChangeBindings(bindings);
29
+ };
30
+
31
+ public render() {
32
+ const { stage, pipeline, bindings } = this.props;
33
+
34
+ const renderSelect = (i: number, binding?: IManifestBindArtifact) => {
35
+ const key = (!binding && 'new') || binding.expectedArtifactId || (binding.artifact && binding.artifact.id);
36
+ return (
37
+ <div className="row" key={key}>
38
+ <div className="col-md-10">
39
+ <StageArtifactSelector
40
+ pipeline={pipeline}
41
+ stage={stage}
42
+ expectedArtifactId={binding && binding.expectedArtifactId}
43
+ artifact={!!binding && binding.artifact}
44
+ onArtifactEdited={(artifact: IArtifact) => this.onChangeBinding(i, { artifact: artifact })}
45
+ onExpectedArtifactSelected={(expectedArtifact: IExpectedArtifact) =>
46
+ this.onChangeBinding(i, { expectedArtifactId: expectedArtifact.id })
47
+ }
48
+ excludedArtifactIds={bindings.map((b) => b.expectedArtifactId)}
49
+ excludedArtifactTypePatterns={[ArtifactTypePatterns.FRONT50_PIPELINE_TEMPLATE]}
50
+ />
51
+ </div>
52
+ {binding && (
53
+ <div className="col-md-2">
54
+ <a className="glyphicon glyphicon-trash" onClick={() => this.onRemoveBinding(i)} />
55
+ </div>
56
+ )}
57
+ </div>
58
+ );
59
+ };
60
+ const renderSelectEditable = (binding: IManifestBindArtifact, i: number) => renderSelect(i, binding);
61
+
62
+ return (
63
+ <>
64
+ {bindings.map(renderSelectEditable)}
65
+ {renderSelect(bindings.length)}
66
+ </>
67
+ );
68
+ }
69
+ }
@@ -0,0 +1,38 @@
1
+ import { get, isEmpty } from 'lodash';
2
+
3
+ import type { ICustomValidator, IPipeline, IStage, IValidatorConfig } from '@spinnaker/core';
4
+
5
+ import { ManifestSource } from '../../../manifest/ManifestSource';
6
+ import { strategyRedBlack } from '../../../rolloutStrategy/redblack.strategy';
7
+
8
+ const MAX_VERSION_HISTORY_ANNOTATION = 'strategy.spinnaker.io/max-version-history';
9
+
10
+ export const deployValidators = (): IValidatorConfig[] => {
11
+ return [
12
+ { type: 'requiredField', fieldName: 'account', fieldLabel: 'Account' },
13
+ { type: 'requiredField', fieldName: 'source', fieldLabel: 'Source' },
14
+ {
15
+ type: 'custom',
16
+ validate: (_pipeline: IPipeline, stage: IStage) => {
17
+ const enabled = get(stage, 'trafficManagement.enabled', false);
18
+ const services = get(stage, 'trafficManagement.options.services', []);
19
+ if (enabled && isEmpty(services)) {
20
+ return `Select at least one <strong>Service</strong> to enable Spinnaker-managed rollout strategy options.`;
21
+ }
22
+ if (enabled && stage.source === ManifestSource.TEXT) {
23
+ const manifests = get(stage, 'manifests', []);
24
+ const replicaSetManifests = manifests.filter((m) => m.kind === 'ReplicaSet');
25
+ const strategy = get(stage, 'trafficManagement.options.strategy');
26
+ const maxVersionHistory = parseInt(
27
+ get(replicaSetManifests, [0, 'metadata', 'annotations', MAX_VERSION_HISTORY_ANNOTATION]),
28
+ 10,
29
+ );
30
+ if (strategy === strategyRedBlack.key && maxVersionHistory < 2) {
31
+ return `The max version history specified in your manifest conflicts with the behavior of the Red/Black rollout strategy. Please update your <strong>${MAX_VERSION_HISTORY_ANNOTATION}</strong> annotation to a value greater than or equal to 2.`;
32
+ }
33
+ }
34
+ return null;
35
+ },
36
+ } as ICustomValidator,
37
+ ];
38
+ };
@@ -0,0 +1,28 @@
1
+ import type { IStage } from '@spinnaker/core';
2
+ import {
3
+ ArtifactReferenceService,
4
+ ExecutionArtifactTab,
5
+ ExecutionDetailsTasks,
6
+ ExpectedArtifactService,
7
+ Registry,
8
+ } from '@spinnaker/core';
9
+
10
+ import { DeployStageConfig } from './DeployStageConfig';
11
+ import { deployValidators } from './deploy.validator';
12
+ import { DeployStatus } from './manifestStatus/DeployStatus';
13
+
14
+ Registry.pipeline.registerStage({
15
+ label: 'Deploy (Cloud Run)',
16
+ description: 'Deploy a Cloud Run manifest yaml/json file.',
17
+ key: 'deployCloudrunManifest',
18
+ cloudProvider: 'cloudrun',
19
+ component: DeployStageConfig,
20
+ executionDetailsSections: [DeployStatus, ExecutionDetailsTasks, ExecutionArtifactTab],
21
+ producesArtifacts: true,
22
+ supportsCustomTimeout: true,
23
+ validators: deployValidators(),
24
+ accountExtractor: (stage: IStage): string[] => (stage.account ? [stage.account] : []),
25
+ configAccountExtractor: (stage: any): string[] => (stage.account ? [stage.account] : []),
26
+ artifactExtractor: ExpectedArtifactService.accumulateArtifacts(['manifestArtifactId', 'requiredArtifactIds']),
27
+ artifactRemover: ArtifactReferenceService.removeArtifactFromFields(['manifestArtifactId', 'requiredArtifactIds']),
28
+ });