diff --git a/packages/amazon/src/aws.module.ts b/packages/amazon/src/aws.module.ts
index d06b6dc7c21..1c0aca5aa88 100644
--- a/packages/amazon/src/aws.module.ts
+++ b/packages/amazon/src/aws.module.ts
@@ -26,22 +26,27 @@ import { AWS_LOAD_BALANCER_MODULE } from './loadBalancer/loadBalancer.module';
import amazonLogo from './logo/amazon.logo.svg';
import { AMAZON_PIPELINE_STAGES_BAKE_AWSBAKESTAGE } from './pipeline/stages/bake/awsBakeStage';
import { AMAZON_PIPELINE_STAGES_CLONESERVERGROUP_AWSCLONESERVERGROUPSTAGE } from './pipeline/stages/cloneServerGroup/awsCloneServerGroupStage';
+import { AMAZON_PIPELINE_STAGES_LAMBDA_DELETE } from './pipeline/stages/deleteLambda';
import { CLOUD_FORMATION_CHANGE_SET_INFO } from './pipeline/stages/deployCloudFormation/CloudFormationChangeSetInfo';
import { CLOUDFORMATION_TEMPLATE_ENTRY } from './pipeline/stages/deployCloudFormation/cloudFormationTemplateEntry.component';
import { DEPLOY_CLOUDFORMATION_STACK_STAGE } from './pipeline/stages/deployCloudFormation/deployCloudFormationStackStage';
import { AWS_EVALUATE_CLOUD_FORMATION_CHANGE_SET_EXECUTION_SERVICE } from './pipeline/stages/deployCloudFormation/evaluateCloudFormationChangeSetExecution.service';
+import { AMAZON_PIPELINE_STAGES_LAMBDA_DEPLOY } from './pipeline/stages/deployLambda';
import { AMAZON_PIPELINE_STAGES_DESTROYASG_AWSDESTROYASGSTAGE } from './pipeline/stages/destroyAsg/awsDestroyAsgStage';
import { AMAZON_PIPELINE_STAGES_DISABLEASG_AWSDISABLEASGSTAGE } from './pipeline/stages/disableAsg/awsDisableAsgStage';
import { AMAZON_PIPELINE_STAGES_DISABLECLUSTER_AWSDISABLECLUSTERSTAGE } from './pipeline/stages/disableCluster/awsDisableClusterStage';
import { AMAZON_PIPELINE_STAGES_ENABLEASG_AWSENABLEASGSTAGE } from './pipeline/stages/enableAsg/awsEnableAsgStage';
import { AMAZON_PIPELINE_STAGES_FINDAMI_AWSFINDAMISTAGE } from './pipeline/stages/findAmi/awsFindAmiStage';
import { AMAZON_PIPELINE_STAGES_FINDIMAGEFROMTAGS_AWSFINDIMAGEFROMTAGSSTAGE } from './pipeline/stages/findImageFromTags/awsFindImageFromTagsStage';
+import { AMAZON_PIPELINE_STAGES_LAMBDA_INVOKE } from './pipeline/stages/invokeLambda';
import { AMAZON_PIPELINE_STAGES_MODIFYSCALINGPROCESS_MODIFYSCALINGPROCESSSTAGE } from './pipeline/stages/modifyScalingProcess/modifyScalingProcessStage';
import { AMAZON_PIPELINE_STAGES_RESIZEASG_AWSRESIZEASGSTAGE } from './pipeline/stages/resizeAsg/awsResizeAsgStage';
import { AMAZON_PIPELINE_STAGES_ROLLBACKCLUSTER_AWSROLLBACKCLUSTERSTAGE } from './pipeline/stages/rollbackCluster/awsRollbackClusterStage';
+import { AMAZON_PIPELINE_STAGES_LAMBDA_ROUTE } from './pipeline/stages/routeLambda';
import { AMAZON_PIPELINE_STAGES_SCALEDOWNCLUSTER_AWSSCALEDOWNCLUSTERSTAGE } from './pipeline/stages/scaleDownCluster/awsScaleDownClusterStage';
import { AMAZON_PIPELINE_STAGES_SHRINKCLUSTER_AWSSHRINKCLUSTERSTAGE } from './pipeline/stages/shrinkCluster/awsShrinkClusterStage';
import { AMAZON_PIPELINE_STAGES_TAGIMAGE_AWSTAGIMAGESTAGE } from './pipeline/stages/tagImage/awsTagImageStage';
+import { AMAZON_PIPELINE_STAGES_LAMBDA_UPDATE } from './pipeline/stages/updateCodeLambda';
import { AWS_REACT_MODULE } from './reactShims/aws.react.module';
import { AMAZON_SEARCH_SEARCHRESULTFORMATTER } from './search/searchResultFormatter';
import { AWS_SECURITY_GROUP_MODULE } from './securityGroup/securityGroup.module';
@@ -114,6 +119,11 @@ module(AMAZON_MODULE, [
INSTANCE_SECURITY_GROUPS_COMPONENT,
INSTANCE_DNS_COMPONENT,
AMAZON_INSTANCE_INFORMATION_COMPONENT,
+ AMAZON_PIPELINE_STAGES_LAMBDA_DELETE,
+ AMAZON_PIPELINE_STAGES_LAMBDA_DEPLOY,
+ AMAZON_PIPELINE_STAGES_LAMBDA_INVOKE,
+ AMAZON_PIPELINE_STAGES_LAMBDA_UPDATE,
+ AMAZON_PIPELINE_STAGES_LAMBDA_ROUTE,
]).config(() => {
CloudProviderRegistry.registerProvider('aws', {
name: 'Amazon',
diff --git a/packages/amazon/src/pipeline/stages/deleteLambda/index.ts b/packages/amazon/src/pipeline/stages/deleteLambda/index.ts
index a2bcdd62906..9df8bc99929 100644
--- a/packages/amazon/src/pipeline/stages/deleteLambda/index.ts
+++ b/packages/amazon/src/pipeline/stages/deleteLambda/index.ts
@@ -1,11 +1,17 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
+import { module } from 'angular';
+
import { Registry, SETTINGS } from '@spinnaker/core';
-import { lambdaDeleteStage } from './LambdaDeleteStage';
+import { lambdaDeleteStage } from './LambdaDeleteStage';
export * from './LambdaDeleteStage';
-if (SETTINGS.feature.lambdaAdditionalStages) {
- Registry.pipeline.registerStage(lambdaDeleteStage);
-}
+export const AMAZON_PIPELINE_STAGES_LAMBDA_DELETE = 'spinnaker.amazon.pipeline.stage.Aws.LambdaDeleteStage';
+
+module(AMAZON_PIPELINE_STAGES_LAMBDA_DELETE, []).config(function () {
+ if (SETTINGS.feature.lambdaAdditionalStages) {
+ Registry.pipeline.registerStage(lambdaDeleteStage);
+ }
+});
diff --git a/packages/amazon/src/pipeline/stages/deployLambda/components/AwsLambdaFunctionStageForm.tsx b/packages/amazon/src/pipeline/stages/deployLambda/components/AwsLambdaFunctionStageForm.tsx
index bbe837f855b..9e03752a57b 100644
--- a/packages/amazon/src/pipeline/stages/deployLambda/components/AwsLambdaFunctionStageForm.tsx
+++ b/packages/amazon/src/pipeline/stages/deployLambda/components/AwsLambdaFunctionStageForm.tsx
@@ -15,7 +15,6 @@ import {
TetheredCreatable,
TextInput,
} from '@spinnaker/core';
-import { NumberConcurrencyInput } from '@spinnaker/core/dist/presentation/forms/inputs/NumberConcurrencyInput';
import { BasicSettingsForm, ExecutionRoleForm, LambdaAtEdgeForm, NetworkForm, TriggerEventsForm } from './index';
@@ -96,7 +95,7 @@ export function AwsLambdaFunctionStageForm(props: IFormikStageConfigInjectedProp
help={
}
- input={(props) => }
+ input={(props) => }
/>
) : (
-
+
)
}
required={false}
diff --git a/packages/amazon/src/pipeline/stages/routeLambda/index.ts b/packages/amazon/src/pipeline/stages/routeLambda/index.ts
index 154d3d60cb2..3ae71566143 100644
--- a/packages/amazon/src/pipeline/stages/routeLambda/index.ts
+++ b/packages/amazon/src/pipeline/stages/routeLambda/index.ts
@@ -1,11 +1,18 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
+import { module } from 'angular';
+
import { Registry, SETTINGS } from '@spinnaker/core';
+
import { lambdaRouteStage } from './LambdaRouteStage';
export * from './LambdaRouteStage';
-if (SETTINGS.feature.lambdaAdditionalStages) {
- Registry.pipeline.registerStage(lambdaRouteStage);
-}
+export const AMAZON_PIPELINE_STAGES_LAMBDA_ROUTE = 'spinnaker.amazon.pipeline.stage.Aws.LambdaTrafficRoutingStage';
+
+module(AMAZON_PIPELINE_STAGES_LAMBDA_ROUTE, []).config(function () {
+ if (SETTINGS.feature.lambdaAdditionalStages) {
+ Registry.pipeline.registerStage(lambdaRouteStage);
+ }
+});
diff --git a/packages/amazon/src/pipeline/stages/updateCodeLambda/LambdaUpdateCodeStage.less b/packages/amazon/src/pipeline/stages/updateCodeLambda/LambdaUpdateCodeStage.less
new file mode 100644
index 00000000000..35112591771
--- /dev/null
+++ b/packages/amazon/src/pipeline/stages/updateCodeLambda/LambdaUpdateCodeStage.less
@@ -0,0 +1,5 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+.LambdaCodeUpdateStageConfig {
+}
diff --git a/packages/amazon/src/pipeline/stages/updateCodeLambda/index.ts b/packages/amazon/src/pipeline/stages/updateCodeLambda/index.ts
index f8f0dccc76a..65297db27d3 100644
--- a/packages/amazon/src/pipeline/stages/updateCodeLambda/index.ts
+++ b/packages/amazon/src/pipeline/stages/updateCodeLambda/index.ts
@@ -1,11 +1,18 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
+import { module } from 'angular';
+
import { Registry, SETTINGS } from '@spinnaker/core';
+
import { lambdaUpdateCodeStage } from './LambdaUpdateCodeStage';
export * from './LambdaUpdateCodeStage';
-if (SETTINGS.feature.lambdaAdditionalStages) {
- Registry.pipeline.registerStage(lambdaUpdateCodeStage);
-}
+export const AMAZON_PIPELINE_STAGES_LAMBDA_UPDATE = 'spinnaker.amazon.pipeline.stage.Aws.LambdaUpdateCodeStage';
+
+module(AMAZON_PIPELINE_STAGES_LAMBDA_UPDATE, []).config(function () {
+ if (SETTINGS.feature.lambdaAdditionalStages) {
+ Registry.pipeline.registerStage(lambdaUpdateCodeStage);
+ }
+});
diff --git a/packages/core/src/config/settings.ts b/packages/core/src/config/settings.ts
index 95c2d60cbed..0b009d37e29 100644
--- a/packages/core/src/config/settings.ts
+++ b/packages/core/src/config/settings.ts
@@ -141,6 +141,7 @@ export interface ISpinnakerSettings {
};
stashTriggerInfo?: string;
pollSchedule: number;
+ tasksViewLimitPerPage: number;
providers?: {
[key: string]: IProviderSettings; // allows custom providers not typed in here (good for testing too)
};
diff --git a/packages/core/src/deploymentStrategy/strategies/redblack/AdditionalFields.tsx b/packages/core/src/deploymentStrategy/strategies/redblack/AdditionalFields.tsx
index 1142429eb42..b243a78d22c 100644
--- a/packages/core/src/deploymentStrategy/strategies/redblack/AdditionalFields.tsx
+++ b/packages/core/src/deploymentStrategy/strategies/redblack/AdditionalFields.tsx
@@ -8,72 +8,101 @@ export interface IRedBlackStrategyAdditionalFieldsProps extends IDeploymentStrat
command: IRedBlackCommand;
}
-export const AdditionalFields = ({ command, onChange }: IRedBlackStrategyAdditionalFieldsProps) => (
-
-
-
-
-
-
-
-
-
- onChange('maxRemainingAsgs', e.target.value)}
- min="2"
- />
-
-
-
- onChange('delayBeforeDisableSec', e.target.value)}
- placeholder="0"
- />
- seconds
-
- {command.scaleDown && (
-
-
-
onChange('delayBeforeScaleDownSec', e.target.value)}
- placeholder="0"
- />
- seconds
+export class AdditionalFields extends React.Component
{
+ private rollbackOnFailureChange = (e: React.ChangeEvent) => {
+ this.props.command.rollback.onFailure = e.target.checked;
+ this.forceUpdate();
+ };
+
+ private scaleDownChange = (e: React.ChangeEvent) => {
+ this.props.command.scaleDown = e.target.checked;
+ this.forceUpdate();
+ };
+
+ private maxRemainingAsgsChange = (e: React.ChangeEvent) => {
+ this.props.command.maxRemainingAsgs = parseInt(e.target.value, 10);
+ this.forceUpdate();
+ };
+
+ private delayBeforeDisableSecChange = (e: React.ChangeEvent) => {
+ this.props.command.delayBeforeDisableSec = parseInt(e.target.value, 10);
+ this.forceUpdate();
+ };
+
+ private delayBeforeScaleDownSecChange = (e: React.ChangeEvent) => {
+ this.props.command.delayBeforeScaleDownSec = parseInt(e.target.value, 10);
+ this.forceUpdate();
+ };
+
+ public render() {
+ const { command } = this.props;
+ return (
+
- )}
-
-);
+ );
+ }
+}
diff --git a/packages/core/src/pipeline/config/services/PipelineConfigService.ts b/packages/core/src/pipeline/config/services/PipelineConfigService.ts
index 0d32a9943ae..8fd8c6d5331 100644
--- a/packages/core/src/pipeline/config/services/PipelineConfigService.ts
+++ b/packages/core/src/pipeline/config/services/PipelineConfigService.ts
@@ -142,6 +142,9 @@ export class PipelineConfigService {
private static groupStagesByRequisiteStageRefIds(pipeline: IPipeline) {
return pipeline.stages.reduce((acc, obj) => {
const parent = obj['refId'];
+ if (obj['requisiteStageRefIds'] === undefined) {
+ obj['requisiteStageRefIds'] = [];
+ }
obj.requisiteStageRefIds.forEach((child) => {
const values = acc.get(child);
if (values && values.length) {
diff --git a/packages/core/src/pipeline/config/stages/overrideTimeout/OverrideTimeout.tsx b/packages/core/src/pipeline/config/stages/overrideTimeout/OverrideTimeout.tsx
index 8b31a7e9696..841092e09d1 100644
--- a/packages/core/src/pipeline/config/stages/overrideTimeout/OverrideTimeout.tsx
+++ b/packages/core/src/pipeline/config/stages/overrideTimeout/OverrideTimeout.tsx
@@ -1,4 +1,4 @@
-import { get } from 'lodash';
+import { get, isNumber } from 'lodash';
import { Duration } from 'luxon';
import React from 'react';
@@ -10,7 +10,7 @@ const { useEffect, useState } = React;
export interface IOverrideTimeoutConfigProps {
stageConfig: IStageConfig;
- stageTimeoutMs: number;
+ stageTimeoutMs: number | any;
updateStageField: (changes: Partial
) => void;
}
@@ -41,8 +41,13 @@ export const OverrideTimeout = (props: IOverrideTimeoutConfigProps) => {
}, [props.stageTimeoutMs]);
const stageChanged = () => {
- if (props.stageTimeoutMs !== undefined) {
+ if (props.stageTimeoutMs !== undefined && !isExpression) {
enableTimeout();
+ } else if (props.stageTimeoutMs !== undefined && isExpression) {
+ setOverrideTimeout(true);
+ props.updateStageField({
+ stageTimeoutMs: props.stageTimeoutMs || null,
+ });
} else {
clearTimeout();
}
@@ -74,6 +79,10 @@ export const OverrideTimeout = (props: IOverrideTimeoutConfigProps) => {
};
const isConfigurable = !!get(props.stageConfig, 'supportsCustomTimeout');
+ const isExpression =
+ props.stageTimeoutMs !== undefined && props.stageTimeoutMs !== null && !isNumber(props.stageTimeoutMs)
+ ? props.stageTimeoutMs.includes('${')
+ : false;
if (isConfigurable) {
return (
@@ -94,7 +103,7 @@ export const OverrideTimeout = (props: IOverrideTimeoutConfigProps) => {
- {overrideTimeout && (
+ {overrideTimeout && !isExpression && (
@@ -123,6 +132,17 @@ export const OverrideTimeout = (props: IOverrideTimeoutConfigProps) => {
)}
+ {overrideTimeout && isExpression && (
+
+
+
+
+ Resolved at runtime from expression: {props.stageTimeoutMs}
+
+
+
+
+ )}
>
);
} else {
diff --git a/packages/core/src/pipeline/config/validation/PipelineConfigValidator.ts b/packages/core/src/pipeline/config/validation/PipelineConfigValidator.ts
index 19c2f7f3dcc..2e37c59b415 100644
--- a/packages/core/src/pipeline/config/validation/PipelineConfigValidator.ts
+++ b/packages/core/src/pipeline/config/validation/PipelineConfigValidator.ts
@@ -54,6 +54,10 @@ export interface ICustomValidator extends IStageOrTriggerValidator, IValidatorCo
[k: string]: any;
}
+function isNumberOrSpel(valInput: any) {
+ return (isNumber(valInput) && valInput > 0) || (typeof valInput === 'string' && valInput.includes('${'));
+}
+
export class PipelineConfigValidator {
private static validators: Map
= new Map();
private static validationStream: Subject = new Subject();
@@ -151,7 +155,7 @@ export class PipelineConfigValidator {
);
}
- if (stage.stageTimeoutMs !== undefined && !(isNumber(stage.stageTimeoutMs) && stage.stageTimeoutMs > 0)) {
+ if (stage.stageTimeoutMs !== undefined && !isNumberOrSpel(stage.stageTimeoutMs)) {
stageValidations.set(stage, [
...(stageValidations.get(stage) || []),
'Stage is configured to fail after a specific amount of time, but no time is set.',
diff --git a/packages/core/src/presentation/forms/inputs/NumberConcurrencyInput.tsx b/packages/core/src/presentation/forms/inputs/NumberConcurrencyInput.tsx
deleted file mode 100644
index 81970637415..00000000000
--- a/packages/core/src/presentation/forms/inputs/NumberConcurrencyInput.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import React from 'react';
-
-import { useInternalValidator } from './hooks';
-import type { IFormInputProps, OmitControlledInputPropsFrom } from './interface';
-import { orEmptyString, validationClassName } from './utils';
-import type { IValidator } from '../validation';
-import { composeValidators, Validators } from '../validation';
-
-interface INumberInputProps extends IFormInputProps, OmitControlledInputPropsFrom> {
- inputClassName?: string;
-}
-
-const isNumber = (val: any): val is number => typeof val === 'number';
-
-export function NumberConcurrencyInput(props: INumberInputProps) {
- const { value, validation, inputClassName, ...otherProps } = props;
-
- const minMaxValidator: IValidator = (val: any, label?: string) => {
- const minValidator = isNumber(props.min) ? Validators.minValue(props.min) : undefined;
- const maxValidator = isNumber(props.max) ? Validators.maxValue(props.max) : undefined;
- const validator = composeValidators([minValidator, maxValidator]);
- return validator ? validator(val, label) : null;
- };
-
- useInternalValidator(validation, minMaxValidator);
-
- const className = `NumberInput form-control ${orEmptyString(inputClassName)} ${validationClassName(validation)}`;
- return ;
-}
diff --git a/packages/core/src/task/task.dataSource.js b/packages/core/src/task/task.dataSource.js
index 03f9ce7dc54..515e1a9c771 100644
--- a/packages/core/src/task/task.dataSource.js
+++ b/packages/core/src/task/task.dataSource.js
@@ -2,6 +2,7 @@ import * as angular from 'angular';
import { ApplicationDataSourceRegistry } from '../application/service/ApplicationDataSourceRegistry';
import { CLUSTER_SERVICE } from '../cluster/cluster.service';
+import { SETTINGS } from '../config';
import { TaskReader } from './task.read.service';
export const CORE_TASK_TASK_DATASOURCE = 'spinnaker.core.task.dataSource';
@@ -14,8 +15,23 @@ angular.module(CORE_TASK_TASK_DATASOURCE, [CLUSTER_SERVICE]).run([
return $q.when(angular.isArray(tasks) ? tasks : []);
};
- const loadTasks = (application) => {
- return TaskReader.getTasks(application.name);
+ const loadPaginatedTasks = async (application, page = 1) => {
+ let limitPerPage = SETTINGS.tasksViewLimitPerPage;
+ const tasks = await TaskReader.getTasks(application.name, [], limitPerPage, page);
+ if (tasks.length === limitPerPage) {
+ return tasks.concat(await loadPaginatedTasks(application, page + 1));
+ } else {
+ return tasks;
+ }
+ };
+
+ const loadTasks = (application, page = 1) => {
+ let limitPerPage = SETTINGS.tasksViewLimitPerPage;
+ if (limitPerPage === undefined) {
+ return TaskReader.getTasks(application.name);
+ } else {
+ return loadPaginatedTasks(application, page);
+ }
};
const loadRunningTasks = (application) => {
diff --git a/packages/core/src/task/task.read.service.ts b/packages/core/src/task/task.read.service.ts
index efb273b3edc..14662fc5ac6 100644
--- a/packages/core/src/task/task.read.service.ts
+++ b/packages/core/src/task/task.read.service.ts
@@ -8,10 +8,19 @@ import { OrchestratedItemTransformer } from '../orchestratedItem/orchestratedIte
export class TaskReader {
private static activeStatuses: string[] = ['RUNNING', 'SUSPENDED', 'NOT_STARTED'];
- public static getTasks(applicationName: string, statuses: string[] = []): PromiseLike {
+ public static getTasks(
+ applicationName: string,
+ statuses: string[] = [],
+ limitPerPage: number = null,
+ page: number = null,
+ ): PromiseLike {
return REST('/applications')
.path(applicationName, 'tasks')
- .query({ statuses: statuses.join(',') })
+ .query({
+ statuses: statuses.join(','),
+ limit: limitPerPage,
+ page: page,
+ })
.get()
.then((tasks: ITask[]) => {
tasks.forEach((task) => this.setTaskProperties(task));
diff --git a/yarn.lock b/yarn.lock
index 3d3034a9dea..c75f48e9d1a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -18292,10 +18292,10 @@ vite-plugin-svgr@^0.3.0:
dependencies:
"@svgr/core" "^5.5.0"
-vite@2.9.16:
- version "2.9.16"
- resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.16.tgz#daf7ba50f5cc37a7bf51b118ba06bc36e97898e9"
- integrity sha512-X+6q8KPyeuBvTQV8AVSnKDvXoBMnTx8zxh54sOwmmuOdxkjMmEJXH2UEchA+vTMps1xw9vL64uwJOWryULg7nA==
+vite@2.9.18:
+ version "2.9.18"
+ resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.18.tgz#74e2a83b29da81e602dac4c293312cc575f091c7"
+ integrity sha512-sAOqI5wNM9QvSEE70W3UGMdT8cyEn0+PmJMTFvTB8wB0YbYUWw3gUbY62AOyrXosGieF2htmeLATvNxpv/zNyQ==
dependencies:
esbuild "^0.14.27"
postcss "^8.4.13"