From 2faf35a59e001b5cc4bda562e97c0bcefb850739 Mon Sep 17 00:00:00 2001
From: Yugandharkumar
Date: Mon, 13 Feb 2023 14:35:15 +0530
Subject: [PATCH 01/38] Create Dockerfile
---
docker/ubi8/Dockerfile | 33 +++++++++++++++++++++++++++++++++
1 file changed, 33 insertions(+)
create mode 100644 docker/ubi8/Dockerfile
diff --git a/docker/ubi8/Dockerfile b/docker/ubi8/Dockerfile
new file mode 100644
index 00000000000..db25270ce35
--- /dev/null
+++ b/docker/ubi8/Dockerfile
@@ -0,0 +1,33 @@
+FROM quay.io/opsmxpublic/ubi8-jre-11:v1
+MAINTAINER sig-platform@spinnaker.io
+LABEL name='deck'
+LABEL maintainer='info@opsmx.io'
+LABEL release=2
+LABEL version='1.21.1'
+LABEL summary='Red Hat certified Open Enterprise Spinnaker ubi8 container image for deck'
+LABEL description='Certified Open Enterprise Spinnaker is an Enterprise grade, Red Hat certified and OpsMx supported release of the popular and critically acclaimed Continuous Delivery platform Spinnaker'
+LABEL vendor='OpsMx'
+WORKDIR /opt/deck
+RUN yum -y install java-11-openjdk-headless.x86_64 wget vim net-tools curl nettle
+RUN yum -y update
+COPY docker /opt/deck/docker
+COPY docker/run-apache2.sh docker/run-apache2.sh
+COPY docker/setup-apache2.sh docker/setup-apache2.sh
+RUN chmod -R 777 docker/setup-apache2.sh
+RUN docker/setup-apache2.sh
+COPY build/webpack /opt/deck/html
+RUN chown -R apache:apache /opt/deck
+USER root
+RUN chown -Rf apache:root /var/log/httpd && chmod -Rf 775 /var/log/httpd
+RUN chgrp -Rf root /var/lib && chmod -Rf g+w /var/lib
+RUN chgrp -Rf root /etc/httpd && chmod -Rf g+w /etc/httpd
+RUN chgrp -Rf root /opt/deck && chmod -Rf g+w /opt/deck
+RUN chgrp -Rf root /var/lock && chmod -Rf g+w /var/lock
+RUN chgrp -Rf root /run/lock && chmod -Rf g+w /run/lock
+RUN chown -Rf apache:root /var/run/httpd && chmod -Rf 775 /var/run/httpd
+RUN chmod g+w /etc/passwd
+RUN chown apache /opt/deck/html/settings-local.js
+
+USER apache
+
+CMD docker/run-apache2.sh
From 249f9e189d25c8c7e610d6fa678d469d88a50cad Mon Sep 17 00:00:00 2001
From: Yugandharkumar
Date: Mon, 13 Feb 2023 14:35:54 +0530
Subject: [PATCH 02/38] Create runapache2.sh
---
docker/ubi8/runapache2.sh | 106 ++++++++++++++++++++++++++++++++++++++
1 file changed, 106 insertions(+)
create mode 100644 docker/ubi8/runapache2.sh
diff --git a/docker/ubi8/runapache2.sh b/docker/ubi8/runapache2.sh
new file mode 100644
index 00000000000..24a7b248257
--- /dev/null
+++ b/docker/ubi8/runapache2.sh
@@ -0,0 +1,106 @@
+# Set any missing env variables used to configure deck
+
+_DECK_HOST=$DECK_HOST
+_DECK_PORT=$DECK_PORT
+_API_HOST=$API_HOST
+_DECK_CERT_PATH=$DECK_CERT
+_DECK_KEY_PATH=$DECK_KEY
+_PASSPHRASE=$PASSPHRASE
+
+if [ ! -e "/etc/redhat-release" ]; then
+ dir=apache2
+ sites=/etc/$dir/sites-available
+ conf=/etc/$dir
+ a2=true
+else
+ dir=httpd
+ sites=/etc/$dir/conf.d
+ conf=/etc/$dir/conf.d
+ a2=false
+fi
+
+if [ -z "$_DECK_HOST" ];
+then
+ _DECK_HOST=0.0.0.0
+fi
+
+if [ -z "$_DECK_PORT" ];
+then
+ _DECK_PORT=9000
+fi
+
+if [ -z "$_API_HOST" ];
+then
+ _API_HOST=http://localhost:8084
+fi
+
+if [ -z "$_DECK_CERT_PATH" ];
+then
+ cp docker/spinnaker.conf.gen spinnaker.conf
+ # remove ssl config, httpd doesn't start without certs
+ if [ -e "/etc/redhat-release" ]; then
+ rm $conf/ssl.conf
+ fi
+else
+ if [ ! -e "/etc/redhat-release" ]; then
+ a2enmod ssl
+ fi
+ # on RHEL it's enabled through config
+ rm $conf/ssl.conf
+ cp docker/spinnaker.conf.ssl spinnaker.conf
+ sed -ie 's|{%DECK_CERT_PATH%}|'$_DECK_CERT_PATH'|g' spinnaker.conf
+ sed -ie 's|{%DECK_KEY_PATH%}|'$_DECK_KEY_PATH'|g' spinnaker.conf
+fi
+
+# Generate spinnaker.conf site & enable it
+
+sed -ie 's|{%DECK_HOST%}|'$_DECK_HOST'|g' spinnaker.conf
+sed -ie 's|{%DECK_PORT%}|'$_DECK_PORT'|g' spinnaker.conf
+sed -ie 's|{%API_HOST%}|'$_API_HOST'|g' spinnaker.conf
+
+mkdir -p $sites
+mv spinnaker.conf $sites
+
+if [ "a2" == "true" ]; then
+ a2ensite spinnaker
+fi
+
+# Update ports.conf to reflect desired deck host
+
+cp docker/ports.conf.gen ports.conf
+
+sed -ie "s|{%DECK_HOST%}|$_DECK_HOST|g" ports.conf
+sed -ie "s|{%DECK_PORT%}|$_DECK_PORT|g" ports.conf
+sed -ie "s|apache2|$dir|g" ports.conf
+mv ports.conf $conf/ports.conf
+
+# Create a passphrase file to inject the SSL passphrase into apache's startup
+
+cp docker/passphrase.gen passphrase
+
+sed -ie "s|{%PASSPHRASE%}|$_PASSPHRASE|g" passphrase
+
+# Clear password from env vars
+
+_PASSPHRASE=""
+PASSPHRASE=""
+
+chmod +x passphrase
+mv passphrase /etc/$dir/passphrase
+
+if [ -e /opt/spinnaker/config/settings.js ];
+then
+ cp /opt/spinnaker/config/settings.js /opt/deck/html/settings.js
+fi
+
+if [ -e /opt/spinnaker/config/settings-local.js ];
+then
+ cp /opt/spinnaker/config/settings-local.js /opt/deck/html/settings-local.js
+fi
+
+if [ ! -e "/etc/redhat-release" ]; then
+ apache2ctl -D FOREGROUND
+else
+ sed -ie "s|Listen 80|#Listen 80|" /etc/httpd/conf/httpd.conf
+ exec /usr/sbin/apachectl -DFOREGROUND
+fi
From dd16181a78cce237d9c82c3bd85651cbf7e23aab Mon Sep 17 00:00:00 2001
From: Yugandharkumar
Date: Mon, 13 Feb 2023 14:36:25 +0530
Subject: [PATCH 03/38] Create setupapache2.sh
---
docker/ubi8/setupapache2.sh | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
create mode 100644 docker/ubi8/setupapache2.sh
diff --git a/docker/ubi8/setupapache2.sh b/docker/ubi8/setupapache2.sh
new file mode 100644
index 00000000000..fce920bba60
--- /dev/null
+++ b/docker/ubi8/setupapache2.sh
@@ -0,0 +1,30 @@
+if [ ! -e "/etc/redhat-release" ]; then
+ user=www-data
+ group=$user
+ app=apache2
+
+ apt-get update
+ apt-get install $app -y
+ service $app stop
+ a2enmod proxy proxy_ajp proxy_http rewrite deflate headers proxy_balancer proxy_connect proxy_html xml2enc
+else
+ user=apache
+ group=$user
+ app=httpd
+
+ yum update
+ maj=$(cat /etc/system-release-cpe | awk -F: '{ print $5 }' | awk -F. '{ print $1 }')
+ if [ "$maj" == "7" ];then
+ prep="httpd24-"
+ else
+ prep=""
+ fi
+ yum install -y ${prep}mod_proxy_html mod_ssl $app
+fi
+
+chown -R $user:$group /etc/$app
+for dir in /var/lib /var/run /var/log;
+do
+ mkdir -p $dir/$app
+ chown -R $user:$group $dir/$app
+done
From 082f4e66a995bcb8c45378d44f5b85410f22c5d3 Mon Sep 17 00:00:00 2001
From: Yugandharkumar
Date: Tue, 14 Feb 2023 13:28:48 +0530
Subject: [PATCH 04/38] Create Preserve-commits.yml
---
.github/workflows/Preserve-commits.yml | 30 ++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
create mode 100644 .github/workflows/Preserve-commits.yml
diff --git a/.github/workflows/Preserve-commits.yml b/.github/workflows/Preserve-commits.yml
new file mode 100644
index 00000000000..c96620c756e
--- /dev/null
+++ b/.github/workflows/Preserve-commits.yml
@@ -0,0 +1,30 @@
+name: GitHub Actions For Commit Preserve
+run-name: ${{ github.actor }} is testing out GitHub Actions
+
+on:
+ pull_request:
+ types:
+ - closed
+jobs:
+ Explore-GitHub-Actions:
+ if: github.event.pull_request.merged == true
+ runs-on: ubuntu-latest
+ steps:
+ - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
+ - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
+ - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
+ - name: Check out repository code
+ uses: actions/checkout@v3
+ - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
+ - name: List files in the repository
+ run: |
+ # ls ${{ github.workspace }}
+ git branch
+ touch Dev_commits
+ git config --global user.email "yugaa22@gmail.com"
+ git config --global user.name "yugaa22"
+ git log -1 | grep commit | awk '{print $2}' >> Dev_commits
+ git add .
+ git commit -m "Developper_commits"
+ git push
+ - run: echo "🍏 This job's status is ${{ job.status }}."
From 9b8af2792090fd94723930997d99c48d7a4176fc Mon Sep 17 00:00:00 2001
From: Siddhu
Date: Tue, 11 Apr 2023 17:18:53 +0530
Subject: [PATCH 05/38] Update docker file
---
docker/ubi8/Dockerfile | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docker/ubi8/Dockerfile b/docker/ubi8/Dockerfile
index db25270ce35..a3168516114 100644
--- a/docker/ubi8/Dockerfile
+++ b/docker/ubi8/Dockerfile
@@ -1,4 +1,4 @@
-FROM quay.io/opsmxpublic/ubi8-jre-11:v1
+FROM quay.io/opsmxpublic/ubifips:8.7
MAINTAINER sig-platform@spinnaker.io
LABEL name='deck'
LABEL maintainer='info@opsmx.io'
@@ -8,7 +8,7 @@ LABEL summary='Red Hat certified Open Enterprise Spinnaker ubi8 container image
LABEL description='Certified Open Enterprise Spinnaker is an Enterprise grade, Red Hat certified and OpsMx supported release of the popular and critically acclaimed Continuous Delivery platform Spinnaker'
LABEL vendor='OpsMx'
WORKDIR /opt/deck
-RUN yum -y install java-11-openjdk-headless.x86_64 wget vim net-tools curl nettle
+RUN yum -y install java-17-openjdk-devel wget vim net-tools curl nettle
RUN yum -y update
COPY docker /opt/deck/docker
COPY docker/run-apache2.sh docker/run-apache2.sh
From 3bcfccb4382ac29877554c8105329aece7c8c287 Mon Sep 17 00:00:00 2001
From: Yugandharkumar
Date: Mon, 17 Apr 2023 22:14:43 +0530
Subject: [PATCH 06/38] Create deck-oes.yml
---
.github/workflows/deck-oes.yml | 53 ++++++++++++++++++++++++++++++++++
1 file changed, 53 insertions(+)
create mode 100644 .github/workflows/deck-oes.yml
diff --git a/.github/workflows/deck-oes.yml b/.github/workflows/deck-oes.yml
new file mode 100644
index 00000000000..339f4f0fe51
--- /dev/null
+++ b/.github/workflows/deck-oes.yml
@@ -0,0 +1,53 @@
+name: Branch Build deck
+
+on:
+ workflow_call:
+ push:
+ branches:
+ - OES-1.30.x-master-adhoc
+
+env:
+ GRADLE_OPTS: -Dorg.gradle.daemon=false -Xmx6g -Xms6g
+ CONTAINER_REGISTRY: quay.io/opsmxpublic
+
+jobs:
+ branch-build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v2
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v2
+ - uses: actions/setup-java@v2
+ with:
+ java-version: 17
+ distribution: 'temurin'
+ cache: 'gradle'
+ - name: Prepare build variables
+ id: build_variables
+ run: |
+ echo ::set-output name=REPO::ubi8-deck-cve
+ echo ::set-output name=VERSION::"$(git rev-parse --short HEAD)-$(date --utc +'%Y%m%d%H%M')"
+ - name: Login to Quay
+ uses: docker/login-action@v1
+ # use service account flow defined at: https://github.com/docker/login-action#service-account-based-authentication-1
+ with:
+ registry: quay.io
+ username: ${{ secrets.QUAY_USERNAME }}
+ password: ${{ secrets.QUAY_KEY }}
+ - name: Build
+ env:
+ ORG_GRADLE_PROJECT_version: ${{ steps.build_variables.outputs.VERSION }}
+ run: ./gradlew --no-daemon -PenableCrossCompilerPlugin=true
+
+ - name: dockerBuildpush
+ uses: docker/build-push-action@v2
+ with:
+ context: .
+ file: docker/ubi8/Dockerfile
+ push: true
+ tags: |
+ "${{ env.CONTAINER_REGISTRY }}/${{ steps.build_variables.outputs.REPO }}:${{ steps.build_variables.outputs.VERSION }}"
From b417cb903282a007d0cc6973d8f69bb84ef29b83 Mon Sep 17 00:00:00 2001
From: Yugandharkumar
Date: Mon, 17 Apr 2023 22:17:06 +0530
Subject: [PATCH 07/38] Update deck-oes.yml
---
.github/workflows/deck-oes.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/deck-oes.yml b/.github/workflows/deck-oes.yml
index 339f4f0fe51..69f8c445530 100644
--- a/.github/workflows/deck-oes.yml
+++ b/.github/workflows/deck-oes.yml
@@ -23,8 +23,8 @@ jobs:
uses: docker/setup-buildx-action@v2
- uses: actions/setup-java@v2
with:
- java-version: 17
- distribution: 'temurin'
+ java-version: 11
+ distribution: 'zulu'
cache: 'gradle'
- name: Prepare build variables
id: build_variables
From 8aa8ec413a74ce9cae54db30663381f83026b5ff Mon Sep 17 00:00:00 2001
From: Yugandharkumar
Date: Wed, 3 May 2023 16:30:39 +0530
Subject: [PATCH 08/38] Update deck-oes.yml
---
.github/workflows/deck-oes.yml | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/deck-oes.yml b/.github/workflows/deck-oes.yml
index 69f8c445530..c8c437861b0 100644
--- a/.github/workflows/deck-oes.yml
+++ b/.github/workflows/deck-oes.yml
@@ -41,8 +41,9 @@ jobs:
- name: Build
env:
ORG_GRADLE_PROJECT_version: ${{ steps.build_variables.outputs.VERSION }}
- run: ./gradlew --no-daemon -PenableCrossCompilerPlugin=true
-
+ run: |
+ ./gradlew --no-daemon -PenableCrossCompilerPlugin=true
+ ls -ltra
- name: dockerBuildpush
uses: docker/build-push-action@v2
with:
From 9388032bce5147795bea6f1b158071097b97a5e1 Mon Sep 17 00:00:00 2001
From: Yugandharkumar
Date: Wed, 3 May 2023 16:37:35 +0530
Subject: [PATCH 09/38] Update deck-oes.yml
---
.github/workflows/deck-oes.yml | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/deck-oes.yml b/.github/workflows/deck-oes.yml
index c8c437861b0..f159aca9005 100644
--- a/.github/workflows/deck-oes.yml
+++ b/.github/workflows/deck-oes.yml
@@ -42,7 +42,16 @@ jobs:
env:
ORG_GRADLE_PROJECT_version: ${{ steps.build_variables.outputs.VERSION }}
run: |
- ./gradlew --no-daemon -PenableCrossCompilerPlugin=true
+ #cp docker/ubi8/setupapache2.sh docker/setup-apache2.sh
+ #cp docker/ubi8/runapache2.sh docker/run-apache2.sh
+
+ # To remove Special characters
+ sed -i 's/\r//' docker/setup-apache2.sh
+ sed -i 's/\r//' docker/run-apache2.sh
+
+ ./gradlew build --no-daemon -PskipTests
+
+ #./gradlew --no-daemon -PenableCrossCompilerPlugin=true
ls -ltra
- name: dockerBuildpush
uses: docker/build-push-action@v2
From bd6138c28cdc2e238178fa4c0c35845b6554dfd3 Mon Sep 17 00:00:00 2001
From: Yugandharkumar
Date: Wed, 3 May 2023 16:43:30 +0530
Subject: [PATCH 10/38] Update deck-oes.yml
---
.github/workflows/deck-oes.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/deck-oes.yml b/.github/workflows/deck-oes.yml
index f159aca9005..57309853b90 100644
--- a/.github/workflows/deck-oes.yml
+++ b/.github/workflows/deck-oes.yml
@@ -42,8 +42,8 @@ jobs:
env:
ORG_GRADLE_PROJECT_version: ${{ steps.build_variables.outputs.VERSION }}
run: |
- #cp docker/ubi8/setupapache2.sh docker/setup-apache2.sh
- #cp docker/ubi8/runapache2.sh docker/run-apache2.sh
+ cp docker/ubi8/setupapache2.sh docker/setup-apache2.sh
+ cp docker/ubi8/runapache2.sh docker/run-apache2.sh
# To remove Special characters
sed -i 's/\r//' docker/setup-apache2.sh
From cad1f30bb84d0f66e16b590f0bce6966b82d90e5 Mon Sep 17 00:00:00 2001
From: Yugandharkumar
Date: Wed, 3 May 2023 16:55:31 +0530
Subject: [PATCH 11/38] Update deck-oes.yml
---
.github/workflows/deck-oes.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/deck-oes.yml b/.github/workflows/deck-oes.yml
index 57309853b90..b4b0da79097 100644
--- a/.github/workflows/deck-oes.yml
+++ b/.github/workflows/deck-oes.yml
@@ -49,9 +49,9 @@ jobs:
sed -i 's/\r//' docker/setup-apache2.sh
sed -i 's/\r//' docker/run-apache2.sh
- ./gradlew build --no-daemon -PskipTests
+ #./gradlew build --no-daemon -PskipTests
- #./gradlew --no-daemon -PenableCrossCompilerPlugin=true
+ ./gradlew --no-daemon -PenableCrossCompilerPlugin=true
ls -ltra
- name: dockerBuildpush
uses: docker/build-push-action@v2
From 29155a21e94550b3c2caa78ff54835f520577c64 Mon Sep 17 00:00:00 2001
From: Yugandharkumar
Date: Wed, 3 May 2023 16:59:12 +0530
Subject: [PATCH 12/38] Update deck-oes.yml
---
.github/workflows/deck-oes.yml | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/deck-oes.yml b/.github/workflows/deck-oes.yml
index b4b0da79097..170d3b3a07a 100644
--- a/.github/workflows/deck-oes.yml
+++ b/.github/workflows/deck-oes.yml
@@ -49,10 +49,11 @@ jobs:
sed -i 's/\r//' docker/setup-apache2.sh
sed -i 's/\r//' docker/run-apache2.sh
- #./gradlew build --no-daemon -PskipTests
+ ./gradlew build --no-daemon -PskipTests
- ./gradlew --no-daemon -PenableCrossCompilerPlugin=true
- ls -ltra
+ #./gradlew --no-daemon -PenableCrossCompilerPlugin=true
+
+ ls -ltra
- name: dockerBuildpush
uses: docker/build-push-action@v2
with:
From 16b9dc4683fc8051e2230f29f6d55576ce48b07b Mon Sep 17 00:00:00 2001
From: sanopsmx
Date: Tue, 2 Feb 2021 17:30:46 +0530
Subject: [PATCH 13/38] Added manual judgment feature.
---
.../application/service/ApplicationReader.ts | 116 ++++
.../modules/core/src/help/help.contents.ts | 529 ++++++++++++++++++
.../manualJudgment/ManualJudgmentApproval.tsx | 192 +++++++
.../src/pipeline/config/stages/stage.html | 163 ++++++
.../pipeline/config/stages/stage.module.js | 393 +++++++++++++
5 files changed, 1393 insertions(+)
create mode 100644 app/scripts/modules/core/src/application/service/ApplicationReader.ts
create mode 100644 app/scripts/modules/core/src/help/help.contents.ts
create mode 100644 app/scripts/modules/core/src/pipeline/config/stages/manualJudgment/ManualJudgmentApproval.tsx
create mode 100644 app/scripts/modules/core/src/pipeline/config/stages/stage.html
create mode 100644 app/scripts/modules/core/src/pipeline/config/stages/stage.module.js
diff --git a/app/scripts/modules/core/src/application/service/ApplicationReader.ts b/app/scripts/modules/core/src/application/service/ApplicationReader.ts
new file mode 100644
index 00000000000..3965b34efd3
--- /dev/null
+++ b/app/scripts/modules/core/src/application/service/ApplicationReader.ts
@@ -0,0 +1,116 @@
+import { API } from 'core/api';
+import { SchedulerFactory } from 'core/scheduler';
+import { Application } from '../application.model';
+import { ApplicationDataSource } from '../service/applicationDataSource';
+import { ApplicationDataSourceRegistry } from './ApplicationDataSourceRegistry';
+import { InferredApplicationWarningService } from './InferredApplicationWarningService';
+
+export interface IApplicationDataSourceAttribute {
+ enabled: string[];
+ disabled: string[];
+}
+
+export interface IApplicationSummary {
+ accounts?: string;
+ aliases?: string;
+ cloudProviders?: string;
+ createTs?: string;
+ description?: string;
+ email?: string;
+ name: string;
+ pdApiKey?: string;
+ updateTs?: string;
+}
+
+export class ApplicationReader {
+ public static listApplications(): PromiseLike {
+ return API.all('applications').useCache().getList();
+ }
+
+ public static getApplicationAttributes(name: string): PromiseLike {
+ return API.one('applications', name)
+ .withParams({ expand: false })
+ .get()
+ .then((fromServer: Application) => {
+ this.splitAttributes(fromServer.attributes, ['accounts', 'cloudProviders']);
+ return fromServer.attributes;
+ });
+ }
+
+ public static getApplicationPermissions(applicationName: string): PromiseLike < any > {
+ return API.one('applications', applicationName)
+ .withParams({
+ expand: false,
+ })
+ .get()
+ .then((application: Application) => {
+ return application.attributes.permissions;
+ });
+ }
+
+ public static getApplication(name: string, expand = true): PromiseLike {
+ return API.one('applications', name)
+ .withParams({ expand: expand })
+ .get()
+ .then((fromServer: Application) => {
+ const configs = ApplicationDataSourceRegistry.getDataSources();
+ const application: Application = new Application(fromServer.name, SchedulerFactory.createScheduler(), configs);
+ application.attributes = fromServer.attributes;
+ this.splitAttributes(application.attributes, ['accounts', 'cloudProviders']);
+ this.setDisabledDataSources(application);
+ application.refresh();
+ return application;
+ });
+ }
+
+ private static splitAttributes(attributes: any, fields: string[]) {
+ fields.forEach((field) => {
+ if (attributes[field]) {
+ if (!Array.isArray(attributes[field])) {
+ attributes[field] = attributes[field].split(',');
+ }
+ } else {
+ attributes[field] = [];
+ }
+ });
+ }
+
+ public static setDisabledDataSources(application: Application) {
+ const allDataSources = application.dataSources;
+ const appDataSources: IApplicationDataSourceAttribute = application.attributes.dataSources;
+
+ if (!appDataSources) {
+ allDataSources.filter((ds) => ds.optIn).forEach((ds) => this.setDataSourceDisabled(ds, application, true));
+ if (InferredApplicationWarningService.isInferredApplication(application)) {
+ allDataSources
+ .filter((ds) => ds.requireConfiguredApp)
+ .forEach((ds) => this.setDataSourceDisabled(ds, application, true));
+ }
+ } else {
+ allDataSources.forEach((ds) => {
+ if (ds.optional) {
+ if (ds.optIn) {
+ this.setDataSourceDisabled(ds, application, !appDataSources.enabled.includes(ds.key));
+ } else {
+ this.setDataSourceDisabled(ds, application, appDataSources.disabled.includes(ds.key));
+ }
+ }
+ });
+ }
+ allDataSources
+ .filter((ds) => ds.requiresDataSource)
+ .forEach((ds) => {
+ const parent = allDataSources.find((p) => p.key === ds.requiresDataSource);
+ if (parent) {
+ this.setDataSourceDisabled(ds, application, parent.disabled);
+ }
+ });
+ }
+
+ private static setDataSourceDisabled(dataSource: ApplicationDataSource, application: Application, disabled: boolean) {
+ dataSource.disabled = disabled;
+ if (dataSource.badge) {
+ application.dataSources.find((ds) => ds.key === dataSource.badge).disabled = disabled;
+ }
+ }
+}
diff --git a/app/scripts/modules/core/src/help/help.contents.ts b/app/scripts/modules/core/src/help/help.contents.ts
new file mode 100644
index 00000000000..bc359e70b76
--- /dev/null
+++ b/app/scripts/modules/core/src/help/help.contents.ts
@@ -0,0 +1,529 @@
+import { HelpContentsRegistry } from './helpContents.registry';
+import { SETTINGS } from 'core/config/settings';
+
+export interface IHelpContents {
+ [key: string]: string;
+}
+
+const helpContents: { [key: string]: string } = {
+ 'core.serverGroup.detail':
+ '(Optional) Detail is a string of free-form alphanumeric characters and hyphens to describe any other variables in naming a cluster.',
+ 'core.serverGroup.strategy':
+ 'The deployment strategy tells Spinnaker what to do with the previous version of the server group.',
+ 'cluster.search': `
+ Quickly filter the displayed server groups by the following fields:
+
+ Build # (e.g. #337 )
+ Jenkins host
+ Jenkins job name
+ Cluster (prefixed, e.g. cluster:myapp-int )
+ VPC (prefixed, e.g. vpc:main )
+ Clusters (comma-separated list, e.g. clusters:myapp-int, myapp-test )
+ Server Group Name
+ Region
+ Account
+ Load Balancer Name
+ Instance ID
+ Labels (comma-separated list of key-value pairs that must all apply to entity, e.g. labels:app=spinnaker, source=prod )
+
+ You can search for multiple words or word fragments. For instance, to find all server groups in a prod stack with "canary" in the details, enter prod canary .
+ To find a particular instance, enter the instance ID. Only the containing server group will be displayed, and the instance
+ will be highlighted for you.
`,
+ 'loadBalancer.search': `
+ Quickly filter the displayed load balancers by the following fields:
+
+ VPC (prefixed, e.g. vpc:main )
+ Server Group Name
+ Load Balancer Name
+ Region
+ Account
+ Instance ID
+
+ You can search for multiple words or word fragments. For instance, to find all load balancers in a prod stack with "canary" in the details, enter prod canary .
`,
+ 'securityGroup.search': `
+ Filter by the following fields:
+
+ VPC (prefixed, e.g. vpc:main )
+ Name
+ Server Group Name
+ Load Balancer Name
+ Region
+ Account
+ `,
+ 'executions.search': `
+ Quickly filter the displayed executions by the following fields:
+ `,
+ 'pipeline.config.triggers.respectQuietPeriod': `
+ The quiet period is a system operator designated period of time when automated pipelines and deploys should not run.
`,
+ 'pipeline.config.expectedArtifact':
+ 'Artifacts required for trigger to execute. Only one of the artifacts needs to be present for the trigger to execute.',
+ 'pipeline.config.artifact.help': `
+ There are certain types of triggers (e.g. Pub/Sub triggers) that can produce artifacts and inject them into the execution context for a pipeline.
+ You can specify artifacts that your pipeline expects to be present in the execution context in this section.
`,
+ 'pipeline.config.artifact.missingPolicy': `
+ The behavior of the pipeline if the Artifact is missing from the pipeline execution.
`,
+ 'pipeline.config.artifact.name': `
+ The name of the Artifact.
`,
+ 'pipeline.config.artifact.type': `
+ The type of the Artifact, e.g. 'gcs/object' or 'rpm'.
`,
+ 'pipeline.config.lock.allowUnlockUi': `
+ Checked - the pipeline can be unlocked via the Spinnaker UI.
+ Unchecked - the pipeline can only be unlocked via the Spinnaker API.
`,
+ 'pipeline.config.lock.description': `
+ Friendly description of why this pipeline is locked.
+ Please include an email address or slack channel as appropriate.
`,
+ 'pipeline.config.optionalStage': `
+ When this option is enabled, stage will only execute when the supplied expression evaluates true.
+ The expression does not need to be wrapped in \${ and }.
+ If this expression evaluates to false, the stages following this stage will still execute.
`,
+ 'pipeline.config.checkPreconditions.failPipeline': `
+ Checked - the overall pipeline will fail whenever this precondition is false.
+ Unchecked - the overall pipeline will continue executing but this particular branch will stop.
`,
+ 'pipeline.config.checkPreconditions.failureMessage': `
+ This failure message will be shown to the user if the precondition evaluates to false.
`,
+ 'pipeline.config.checkPreconditions.expectedSize': 'Number of server groups in the selected cluster',
+ 'pipeline.config.checkPreconditions.expression': `
+ Value must evaluate to "true".
+ Use of the Spring Expression Language allows for complex evaluations.
`,
+ 'pipeline.config.deploy.template': `
+ Select an existing cluster to use as a template for this deployment, and we'll pre-fill
+ the configuration based on the newest server group in the cluster.
+ If you want to start from scratch, select "None".
+ You can always edit the cluster configuration after you've created it.
`,
+ 'pipeline.config.expectedArtifact.matchArtifact': `
+
+ This specifies which fields in your incoming artifact to match against. Every field that
+ you supply will be used to match against all incoming artifacts. If all specified fields
+ match, the incoming artifact is bound to your pipeline context.
+
+
+ The field comparisons are done against the incoming artifact. Example: if you are parsing
+ artifacts from pub/sub messages via a Jinja template, the comparison will be done after
+ the pub/sub -> Spinnaker artifact translation.
+
+ For example, if you want to match against any GCS object, only supply type = gcs/object. If you also want to restrict the matches by other fields, include those as well.
+ Regex is accepted, so you could for example match on a filepath like so name = .*\\.yaml to match all incoming YAML files.
+ See the reference for more information.
`,
+ 'pipeline.config.expectedArtifact.ifMissing': `
+ If no artifact was supplied by your trigger to match against this expected artifact, you have a few options:
+
+ Attempt to match against an artifact in the prior pipeline execution's context. This ensures that you will always be using the most recently supplied artifact to this pipeline, and is generally a safe choice.
+ If option 1 fails, or isn't specified, you can provide a default artifact with the required fields to use instead.
+ Fail the pipeline if options 1 or 2 fail or aren't selected.
+
+
+ See the reference for more information.
`,
+ 'pipeline.config.expectedArtifact.usePriorExecution': `
+ Attempt to match against an artifact in the prior pipeline execution's context. This ensures that you will always be using the most recently supplied artifact to this pipeline, and is generally a safe choice.
`,
+ 'pipeline.config.expectedArtifact.defaultArtifact': `
+ If your artifact either wasn't supplied from a trigger, or it wasn't found in a prior execution, the artifact specified below will end up in your pipeline's execution context.
+ See the reference for more information.
`,
+ 'pipeline.config.expectedArtifact.gcs.name': `
+ The GCS object name, in the form gs://bucket/path/to/file.yml
.
`,
+ 'pipeline.config.expectedArtifact.defaultGcs.reference': `
+ The GCS object name, optionally appending the version. An example: gs://bucket/file.yml#123948581
`,
+ 'pipeline.config.expectedArtifact.s3.name': `
+ The S3 object name, in the form s3://bucket/path/to/file.yml
.
`,
+ 'pipeline.config.expectedArtifact.defaultS3.reference': `
+ The S3 object name, optionally appending the version. An example: s3://bucket/file.yml#123948581
`,
+ 'pipeline.config.expectedArtifact.oracle.name': `
+ The Oracle object artifact name, in the form oci://bucket/path/file.yml
.
`,
+ 'pipeline.config.expectedArtifact.defaultOracle.reference': `
+ The Oracle object artifact name, optionally appending the version. An example: oci://bucket/file.yml#9ce463aa-d843-4438-b206-5365cd643e2e
`,
+ 'pipeline.config.expectedArtifact.docker.name': `
+ The Docker image name you want to trigger on changes to. By default, this does not include the image tag or digest, only the registry and image repository.
`,
+ 'pipeline.config.expectedArtifact.defaultDocker.reference': `
+ The fully-qualified docker image to deploy. An example: gcr.io/project/image@sha256:59bb771c86
`,
+ 'pipeline.config.expectedArtifact.git.name': `
+ The file's path from the git root, in the form 'path/to/file.json'
`,
+ 'pipeline.config.expectedArtifact.defaultGithub.version': `
+ Either the commit or branch to checkout.
`,
+ 'pipeline.config.expectedArtifact.defaultGithub.reference': `
+ The GitHub API content url the artifact lives under. The domain name may change if you're running GHE.
+ An example for GitHub.com is https://api.github.com/repos/$ORG/$REPO/contents/$FILEPATH
. An example for GitHub Enterprise is https://github.domain.com/api/v3/repos/$ORG/$REPO/contents/$FILEPATH
. See our docs for more info.
`,
+ 'pipeline.config.expectedArtifact.defaultGitlab.version': `
+ Either the commit or branch to checkout.
`,
+ 'pipeline.config.expectedArtifact.defaultGitlab.reference': `
+ The Gitlab API file url the artifact lives under. The domain name may change if you're running your own Gitlab server. The repository and path to files must be URL encoded.
+ An example is https://gitlab.com/api/v4/projects/$ORG%2F$REPO/repository/files/path%2Fto%2Ffile.yml/raw
. See our docs for more info.
`,
+ 'pipeline.config.expectedArtifact.gitrepo.url': 'The location of your Git repository.
',
+ 'pipeline.config.expectedArtifact.gitrepo.branch': 'The branch of the repository you want to use.
',
+ 'pipeline.config.expectedArtifact.gitrepo.checkoutSubpath':
+ 'Check this if you want to specify a subpath; doing so will reduce the size of the generated artifact.
',
+ 'pipeline.config.expectedArtifact.gitrepo.subpath': `
+ The subpath within the Git repository you desire to checkout.
+ e.g.: examples/wordpress/mysql/
`,
+ 'pipeline.config.expectedArtifact.helm.account': `
+ The account contains url the charts can be found
`,
+ 'pipeline.config.expectedArtifact.helm.name': `
+ The name of chart you want to trigger on changes to
`,
+ 'pipeline.config.expectedArtifact.helm.version': `
+ The version of chart you want to trigger on changes to
`,
+ 'pipeline.config.expectedArtifact.defaultBitbucket.reference': `
+ The Bitbucket API file url the artifact lives under. The domain name may change if you're running your own Bitbucket server. The repository and path to files must be URL encoded.
+ An example is https://api.bitbucket.org/1.0/repositories/$ORG/$REPO/raw/$VERSION/$FILEPATH
. See our docs for more info.
`,
+ 'pipeline.config.expectedArtifact.defaultBitbucket.filepath': `
+ The file path within your repo. path/to/file.yml is an example.
`,
+ 'pipeline.config.trigger.helm.chart': `The Helm chart name.`,
+ 'pipeline.config.trigger.helm.version': `The Helm chart version, as semver.`,
+ 'pipeline.config.trigger.helm.version.manual': `The Helm chart version, as an exact version.`,
+ 'pipeline.config.trigger.webhook.source': `
+ Determines the target URL required to trigger this pipeline, as well as how the payload can be transformed into artifacts.
+ `,
+ 'pipeline.config.trigger.webhook.payloadConstraints': `
+ When provided, only a webhook with a payload containing at least the specified key/value pairs will be allowed to trigger this pipeline. For example, if you wanted to lock down the systems/users that can trigger this pipeline via this webhook, you could require the key "secret" and value "something-secret" as a constraint.
+ The constraint values may be supplied as regex.
+ `,
+ 'pipeline.config.trigger.pubsub.attributeConstraints': `
+ Pubsub messages can have system-specific metadata accompanying the payload called attributes .
+ When provided, only a pubsub message with attributes containing at least the specified key/value pairs will be allowed to trigger this pipeline.
+ The constraint value is a java regex string.
+ `,
+ 'pipeline.config.trigger.pubsub.payloadConstraints': `
+
+ When provided, only a pubsub message with a payload containing at least the specified
+ key/value pairs will be allowed to trigger this pipeline. For example, if you wanted
+ to restrict the systems/users that can trigger this pipeline via this pubsub
+ subscription, you could require the key "secret" and value "something-secret" as a constraint.
+
+
+ The key/value pairs are matched against the unprocessed payload body, prior to any
+ transformation using, for example, a Jinja template in a pubsub subscription configuration.
+
+ The constraint value is a java regex string.
+ `,
+ 'pipeline.config.findArtifactFromExecution.considerExecutions': `
+ Select the types of executions to consider. When no selection is made, the default is "any execution".
+ This will always evaluate to the most recent execution matching your provided criteria.
+ `,
+ 'loadBalancer.advancedSettings.healthTimeout':
+ 'Configures the timeout, in seconds, for reaching the healthCheck target. Must be less than the interval.
Default: 5
',
+ 'loadBalancer.advancedSettings.idleTimeout':
+ 'Configures the idle timeout, in seconds. If no data has been sent or received by the time that the idle timeout period elapses, the load balancer closes the connection.
Default: 60
',
+ 'loadBalancer.advancedSettings.deletionProtection':
+ 'To prevent your load balancer from being deleted accidentally, you can enable deletion protection.
Default: false
',
+ 'loadBalancer.advancedSettings.healthInterval':
+ 'Configures the interval, in seconds, between ELB health checks. Must be greater than the timeout.
Default: 10
',
+ 'loadBalancer.advancedSettings.healthyThreshold':
+ 'Configures the number of healthy observations before reinstituting an instance into the ELB’s traffic rotation.
Default: 10
',
+ 'loadBalancer.advancedSettings.unhealthyThreshold':
+ 'Configures the number of unhealthy observations before deservicing an instance from the ELB.
Default: 2
',
+ 'loadBalancer.advancedSettings.loadBalancingCrossZone':
+ 'Cross-zone load balancing distributes traffic evenly across all targets in the Availability Zones enabled for the load balancer.
Default: True
',
+ 'pipeline.config.resizeAsg.action': `
+ Configures the resize action for the target server group.
+
+ Scale Up increases the size of the target server group by an incremental or percentage amount
+ Scale Down decreases the size of the target server group by an incremental or percentage amount
+ Scale to Cluster Size increases the size of the target server group to match the largest server group in the cluster, optionally with an incremental or percentage additional capacity. Additional capacity will not exceed the existing maximum size.
+ Scale to Exact Size adjusts the size of the target server group to match the provided capacity
+ `,
+ 'pipeline.config.resizeAsg.cluster':
+ 'Configures the cluster upon which this resize operation will act. The target specifies what server group to resolve for the operation.
',
+ 'pipeline.config.modifyScalingProcess.cluster':
+ 'Configures the cluster upon which this modify scaling process operation will act. The target specifies what server group to resolve for the operation.
',
+ 'pipeline.config.enableAsg.cluster':
+ 'Configures the cluster upon which this enable operation will act. The target specifies what server group to resolve for the operation.
',
+ 'pipeline.config.disableAsg.cluster':
+ 'Configures the cluster upon which this disable operation will act. The target specifies what server group to resolve for the operation.
',
+ 'pipeline.config.destroyAsg.cluster':
+ 'Configures the cluster upon which this destroy operation will act. The target specifies what server group to resolve for the operation.
',
+ 'pipeline.config.jenkins.trigger.propertyFile':
+ '(Optional) Configures the name to the Jenkins artifact file used to pass in properties to later stages in the Spinnaker pipeline. The contents of this file will now be available as a map under the trigger and accessible via trigger.properties . See Pipeline Expressions docs for more information.
',
+ 'pipeline.config.jenkins.propertyFile':
+ '(Optional) Configures the name to the Jenkins artifact file used to pass in properties to later stages in the Spinnaker pipeline. The contents of this file will now be available as a map under the stage context. See Pipeline Expressions docs for more information.
',
+ 'pipeline.config.travis.job.isFiltered':
+ 'Note that for performance reasons, not all jobs are displayed. Please use the search field to limit the number of jobs.
',
+ 'pipeline.config.travis.trigger.propertyFile':
+ '(Optional) Configures the name to the Travis artifact file used to pass in properties to later stages in the Spinnaker pipeline. The contents of this file will now be available as a map under the trigger and accessible via trigger.properties . See Pipeline Expressions docs for more information.
',
+ 'pipeline.config.travis.propertyFile':
+ '(Optional) Configures the name to the Travis artifact file used to pass in properties to later stages in the Spinnaker pipeline. The contents of this file will now be available as a map under the stage context. See Pipeline Expressions docs for more information.
',
+ 'pipeline.config.bake.skipRegionDetection': `
+ By default, Spinnaker will detect regions to bake in from downstream deploy stages.
+ To prevent failed deploys from accidentally missed regions during the bake process.
+ This setting will disable this detection mechanism.
`,
+ 'pipeline.config.bake.package': `
+ The name of the package you want installed (without any version identifiers).
+ If your build produces a deb file named "myapp_1.27-h343", you would want to enter "myapp" here.
+ If there are multiple packages (space separated), then they will be installed in the order they are entered.
`,
+ 'pipeline.config.bake.packageArtifacts': `
+ Artifacts representing packages you want installed.
+ These artifacts must be either deb or rpm packages, whichever applies to the operating system on your base image.
+ Package artifacts are installed in order, after any packages in the 'Packages' field are installed.
`,
+ 'pipeline.config.docker.bake.targetImage': 'The name of the resulting docker image.
',
+ 'pipeline.config.docker.bake.targetImageTag':
+ 'The tag of the resulting docker image, defaults to commit hash if available.
',
+ 'pipeline.config.docker.bake.organization':
+ 'The name of the organization or repo to use for the resulting docker image.
',
+ 'pipeline.config.bake.baseAmi':
+ '(Optional) If Base AMI is specified, this will be used instead of the Base OS provided',
+ 'pipeline.config.bake.amiSuffix':
+ '
(Optional) String of date in format YYYYMMDDHHmm, default is calculated from timestamp,
',
+ 'pipeline.config.bake.amiName': '(Optional) Default = $package-$arch-$ami_suffix-$store_type
',
+ 'pipeline.config.bake.templateFileName':
+ "(Optional) The explicit packer template to use, instead of resolving one from rosco's configuration.
",
+ 'pipeline.config.bake.varFileName':
+ '(Optional) The name of a json file containing key/value pairs to add to the packer command.
',
+ 'pipeline.config.bake.extendedAttributes':
+ '(Optional) Any additional attributes that you want to pass onto rosco, which will be injected into your packer runtime variables.
',
+ 'pipeline.config.manualJudgment.instructions':
+ '(Optional) Instructions are shown to the user when making a manual judgment.
May contain HTML.
',
+ 'pipeline.config.manualJudgment.propagateAuthentication': `
+ Checked - the pipeline will continue with the permissions of the approver.
+ Unchecked - the pipeline will continue with its current permissions.
`,
+ 'pipeline.config.manualJudgment.judgmentInputs': `
+ (Optional) Entries populate a dropdown displayed when performing a manual judgment.
+ The selected value can be used in a subsequent Check Preconditions stage to determine branching.
+ For example, if the user selects "rollback" from this list of options, that branch can be activated by using the expression:
+ execution.stages[n].context.judgmentInput=="rollback"
`,
+ 'pipeline.config.bake.manifest.expectedArtifact': 'This is the template you want to render.
',
+ 'pipeline.config.bake.manifest.overrideExpressionEvaluation':
+ 'Explicitly evaluate SpEL expressions in overrides just prior to manifest baking. Can be paired with the "Skip SpEL evaluation" option in the Deploy Manifest stage when baking a third-party manifest artifact with expressions not meant for Spinnaker to evaluate as SpEL.
',
+ 'pipeline.config.bake.manifest.templateRenderer': 'This is the engine used for rendering your manifest.
',
+ 'pipeline.config.bake.manifest.helm.chartFilePath': `
+ This is the relative path to the Chart.yaml file within your Git repo.
+ e.g.: helm/my-chart/Chart.yaml
`,
+ 'pipeline.config.bake.manifest.helm.rawOverrides':
+ 'Use --set instead of --set-string when injecting override values. Values injected using --set will be converted to primitive types by Helm.',
+ 'pipeline.config.bake.manifest.kustomize.filePath': `
+ This is the relative path to the kustomization.yaml file within your Git repo.
+ e.g.: examples/wordpress/mysql/kustomization.yaml
`,
+ 'pipeline.config.bake.cf.manifest.name':
+ ' Name should be the same as the expected artifact in the Produces Artifact section.
',
+ 'pipeline.config.bake.cf.manifest.templateArtifact': `
+ This is the manifest template needing resolution. Variables in this template should use double parentheses notation.
+ e.g.:
+ ---
+ buildpack: ((javabuildpack))
+ foo: ((some.nestedKey))
`,
+ 'pipeline.config.bake.cf.manifest.varsArtifact': `
+ These are the variables that will be substituted in the manifest template. These should be yaml files and follow standard convention.
+ e.g.:
+ ---
+ javabuildpack: java_buildpack_offline
+ some:
+ nestedKey: bar
`,
+ 'pipeline.config.haltPipelineOnFailure':
+ 'Immediately halts execution of all running stages and fails the entire execution.',
+ 'pipeline.config.haltBranchOnFailure':
+ 'Prevents any stages that depend on this stage from running, but allows other branches of the pipeline to run.',
+ 'pipeline.config.haltBranchOnFailureFailPipeline':
+ 'Prevents any stages that depend on this stage from running, but allows other branches of the pipeline to run. The pipeline will be marked as failed once complete.',
+ 'pipeline.config.ignoreFailure': 'Continues execution of downstream stages, marking this stage as failed/continuing.',
+ 'pipeline.config.jenkins.markUnstableAsSuccessful.true':
+ 'If Jenkins reports the build status as UNSTABLE, Spinnaker will mark the stage as SUCCEEDED and continue execution of the pipeline.',
+ 'pipeline.config.jenkins.markUnstableAsSuccessful.false': `
+ If Jenkins reports the build status as UNSTABLE,
+ Spinnaker will mark the stage as FAILED; subsequent execution will be determined based on the configuration of the
+ If build fails option for this stage.`,
+ 'pipeline.config.travis.markUnstableAsSuccessful.true':
+ 'If Travis reports the build status as UNSTABLE, Spinnaker will mark the stage as SUCCEEDED and continue execution of the pipeline.',
+ 'pipeline.config.travis.markUnstableAsSuccessful.false': `
+ If Travis reports the build status as UNSTABLE,
+ Spinnaker will mark the stage as TERMINAL; subsequent execution will be determined based on the configuration of the
+ If build fails option for this stage.`,
+ 'pipeline.config.wercker.markUnstableAsSuccessful.true':
+ 'If Wercker reports the build status as UNSTABLE, Spinnaker will mark the stage as SUCCEEDED and continue execution of the pipeline.',
+ 'pipeline.config.wercker.markUnstableAsSuccessful.false': `
+ If Wercker reports the build status as UNSTABLE,
+ Spinnaker will mark the stage as FAILED; subsequent execution will be determined based on the configuration of the
+ If build fails option for this stage.`,
+ 'pipeline.config.cron.expression':
+ 'Format (Year is optional) Seconds Minutes Hour DayOfMonth Month DayOfWeek (Year)
' +
+ 'Example: every 30 minutes
0 0/30 * * * ? ' +
+ 'Example: every Monday at 10 am
0 0 10 ? * 2 ' +
+ 'Note: values for "DayOfWeek" are 1-7, where Sunday is 1, Monday is 2, etc. You can also use MON,TUE,WED, etc.',
+
+ 'cluster.rollback.explicit': `
+
A server group running the previous build will be enabled and appropriately resized.
+ The current server group will be disabled after the resize completes.
+ `,
+ 'cluster.rollback.previous_image': `
+ The current server group will be cloned with the previous build.
+ `,
+ 'pipeline.config.findAmi.cluster': 'The cluster to look at when selecting the image to use in this pipeline.',
+ 'pipeline.config.findAmi.imageNamePattern':
+ 'A regex used to match the name of the image. Must result in exactly one match to succeed. Empty is treated as match any.',
+ 'pipeline.config.dependsOn': 'Declares which stages must be run before this stage begins.',
+ 'pipeline.config.parallel.cancel.queue':
+ 'If concurrent pipeline execution is disabled, then the pipelines that are in the waiting queue will get canceled when the next execution starts. Check this box if you want to keep them in the queue.
',
+ 'pipeline.config.timeout': `
+ Allows you to force the stage to fail if its running time exceeds a specific length.
+ Note: By default, Spinnaker will use sensible timeouts that depend on the stage type and the operations the stage needs to perform at runtime. These defaults can vary based on chosen configuration and other external factors.
+
`,
+ 'pipeline.config.trigger.runAsUser':
+ "The current user must have access to the specified service account, and the service account must have access to the current application. Otherwise, you'll receive an 'Access is denied' error.",
+ 'pipeline.config.trigger.authorizedUser':
+ "The current user must have the permission to approve the manual judgment stage. Otherwise, you'll not be able continue to the next pipeline stage.",
+ 'pipeline.config.script.repoUrl':
+ 'Path to the repo hosting the scripts in Stash. (e.g. CDL/mimir-scripts ). Leave empty to use the default.
',
+ 'pipeline.config.script.repoBranch':
+ 'Git Branch. (e.g. master ). Leave empty to use the master branch.
',
+ 'pipeline.config.script.path':
+ 'Path to the folder hosting the scripts in Stash. (e.g. groovy , python or shell )
',
+ 'pipeline.config.script.command':
+ 'Executable script and parameters. (e.g. script.py --ami-id ${deploymentDetails[0].ami} )
',
+ 'pipeline.config.script.image': '(Optional) image passed down to script execution as IMAGE_ID
',
+ 'pipeline.config.script.account': '(Optional) account passed down to script execution as ENV_PARAM
',
+ 'pipeline.config.script.region': '(Optional) region passed down to script execution as REGION_PARAM
',
+ 'pipeline.config.script.cluster': '(Optional) cluster passed down to script execution as CLUSTER_PARAM
',
+ 'pipeline.config.script.cmc': '(Optional) cmc passed down to script execution as CMC
',
+ 'pipeline.config.script.propertyFile':
+ '(Optional) The name to the properties file produced by the script execution to be used by later stages of the Spinnaker pipeline.
',
+ 'pipeline.config.docker.trigger.tag':
+ '(Optional) If specified, only the tags that match this Java Regular Expression will be triggered. Leave empty to trigger builds on any tag pushed.
Builds will not be triggered off the latest tag or updates to existing tags.
',
+ 'pipeline.config.docker.trigger.digest': 'The SHA256 hash of the image.
',
+ 'pipeline.config.git.trigger.branch':
+ '(Optional) If specified, only pushes to the branches that match this Java Regular Expression will be triggered. Leave empty to trigger builds for every branch.
',
+ 'pipeline.config.git.trigger.githubSecret':
+ '(Optional, but recommended) If specified, verifies GitHub as the sender of this trigger. See GitHub docs for more information.
',
+ 'serverGroupCapacity.useSourceCapacityTrue': `
+ Spinnaker will use the current capacity of the existing server group when deploying a new server group.
+ This setting is intended to support a server group with auto-scaling enabled, where the bounds and desired capacity are controlled by an external process.
+ In the event that there is no existing server group, the deploy will fail.
`,
+ 'serverGroupCapacity.useSourceCapacityFalse':
+ 'The specified capacity is used regardless of the presence or size of an existing server group.
',
+ 'strategy.redblack.scaleDown': `
+ Resizes the target server group to zero instances before disabling it.
+ Select this if you wish to retain the launch configuration for the old server group without running any instances.
`,
+ 'strategy.redblack.maxRemainingAsgs': `
+ Optional : indicates the maximum number of server groups that will remain in this cluster - including the newly created one.
+ If you wish to destroy all server groups except the newly created one, select "Highlander" as the strategy.
+ Minimum value: 2
`,
+ 'strategy.redblack.rollback': `
+ Disable the new server group and ensure that the previous server group is restored to its original capacity.
+ The rollback will only be initiated if instances in the new server group fail to launch and become healthy.
+ Should an error occur disabling or destroying other server groups in the cluster, the new server group will not be rolled back.
+ `,
+ 'strategy.rollingPush.relaunchAll':
+ 'Incrementally terminates each instance in the server group, waiting for a new one to come up before terminating the next one.
',
+ 'strategy.rollingPush.totalRelaunches': 'Total number of instances to terminate and relaunch.
',
+ 'strategy.rollingPush.concurrentRelaunches': 'Number of instances to terminate and relaunch at a time.
',
+ 'strategy.rollingPush.concurrentRelaunches.migration': `
+ Number of instances to terminate and relaunch at a time.
+ Can be expressed as an explicit instance count or as a percentage of instances in server group being migrated.
+ `,
+ 'strategy.rollingPush.order': `
+ Determines the order in which instances will be terminated.
+
Oldest will terminate the oldest instances first
+ Newest will terminate those most recently launched. `,
+ 'strategy.rollingRedBlack.targetPercentages':
+ 'Rolling red black will slowly scale up the new server group. It will resize the new server group by each percentage defined.
',
+ 'strategy.rollingRedBlack.rollback':
+ 'Disable the new server group and ensure that the previous server group is restored to its original capacity.
',
+ 'strategy.monitored.deploySteps':
+ 'Monitored Deploy will scale up the new server group as specified by these per cent steps. After each step, the health of the new server group will be evaluated by the specified deployment monitor.
',
+ 'strategy.monitored.rollback':
+ 'If deploy fails, disable the new server group and ensure that the previous server group is active and restored to its original capacity.
',
+ 'strategy.monitored.destroyFailedAsg':
+ 'If deploy fails and rollback succeeds destroys the server group that failed the deploy instead of just disabling it.
',
+ 'loadBalancers.filter.serverGroups': `
+ Displays all server groups configured to use the load balancer.
+ If the server group is configured to not add new instances to the load balancer, it will be grayed out.
`,
+ 'loadBalancers.filter.instances': `
+ Displays all instances in the context of their parent server group. The color of the instance icon
+ indicates only its health in relation to the load balancer . That is, if the load balancer health check reports the instance
+ as healthy, the instance will appear green - even if other health indicators (Discovery, other load balancers, etc.) report the instance
+ as unhealthy.
+ A red icon indicates the instance is failing the health check for the load balancer.
+ A gray icon indicates the instance is currently detached from the load balancer.
`,
+ 'loadBalancers.filter.onlyUnhealthy': `
+ Filters the list of load balancers and server groups (if enabled)
+ to only show load balancers with instances failing the health check for the load balancer.
`,
+ 'project.cluster.stack':
+ '(Optional field)
Filters displayed clusters by stack.
Enter * to include all stacks; leave blank to omit any clusters with a stack.
Only * is valid for Kubernetes V2 accounts.
',
+ 'project.cluster.detail':
+ '(Optional field)
Filters displayed clusters by detail.
Enter * to include all details; leave blank to omit any clusters with a detail.
Only * is valid for Kubernetes V2 accounts.
',
+ 'instanceType.storageOverridden':
+ 'These storage settings have been cloned from the base server group and differ from the default settings for this instance type.
',
+ 'instanceType.unavailable': 'This instance type is not available for the selected configuration.
',
+ 'execution.forceRebake': `
+ By default, the bakery will not create a new image if the contents of the package and base image have not changed.
+ Instead, it will return the previously baked image, though this behavior is not guaranteed.
+ Select this option to force the bakery to create a new image, regardless of whether or not a matching image exists.
`,
+ 'execution.dryRun': `
+ Select this option to run the pipeline without really executing anything.
+ This is a good way to test parameter-driven behavior, expressions, optional stages, etc.
`,
+ 'user.verification': `
+ Typing into this verification field is annoying! But it serves as a reminder that you are
+ changing something in an account deemed important, and prevents you from accidentally changing something
+ when you meant to click on the "Cancel" button.`,
+ 'pipeline.waitForCompletion':
+ 'if unchecked, marks the stage as successful right away without waiting for the pipeline to complete',
+ 'jenkins.waitForCompletion':
+ 'if unchecked, marks the stage as successful right away without waiting for the Jenkins job to complete',
+ 'travis.waitForCompletion':
+ 'if unchecked, marks the stage as successful right away without waiting for the Travis job to complete',
+ 'wercker.waitForCompletion':
+ 'if unchecked, marks the stage as successful right away without waiting for the Wercker job to complete',
+ 'script.waitForCompletion':
+ 'if unchecked, marks the stage as successful right away without waiting for the script to complete',
+ // eslint-disable-next-line no-useless-escape
+ 'markdown.examples': `
+ Some examples of markdown syntax: \`*italic*\` \`**bold**\` \`[link text](http://url-goes-here)\`
+ `,
+ 'pipeline.config.webhook.payload': 'JSON payload to be added to the webhook call.',
+ 'pipeline.config.webhook.cancelPayload':
+ 'JSON payload to be added to the webhook call when it is called in response to a cancellation.',
+ 'pipeline.config.webhook.waitForCompletion':
+ 'If not checked, we consider the stage succeeded if the webhook returns an HTTP status code 2xx, otherwise it will be failed. If checked, it will poll a status url (defined below) to determine the progress of the stage.',
+ 'pipeline.config.webhook.statusUrlResolutionIsGetMethod': "Use the webhook's URL with GET method as status endpoint.",
+ 'pipeline.config.webhook.statusUrlResolutionIsLocationHeader':
+ "Pick the status url from the Location header of the webhook's response call.",
+ 'pipeline.config.webhook.statusUrlResolutionIsWebhookResponse':
+ "Pick the status url from the JSON returned by the webhook's response call.",
+ 'pipeline.config.webhook.statusUrlJsonPath':
+ "JSON path to the status url in the webhook's response JSON. (i.e. $.buildInfo.url )",
+ 'pipeline.config.webhook.retryStatusCodes':
+ 'Normally, webhook stages only retry on 429 and 5xx status codes. You can specify additional status codes here that will cause the monitor to retry (e.g. 404, 418 )',
+ 'pipeline.config.webhook.waitBeforeMonitor':
+ 'Optional delay (in seconds) to wait before starting to poll the endpoint for monitoring status',
+ 'pipeline.config.webhook.statusJsonPath':
+ "JSON path to the status information in the webhook's response JSON (e.g. $.buildInfo.status ). If left empty, a 200 response from the status endpoint will be treated as a success.",
+ 'pipeline.config.webhook.progressJsonPath':
+ "JSON path to a descriptive message about the progress in the webhook's response JSON. (e.g. $.buildInfo.progress )",
+ 'pipeline.config.webhook.successStatuses':
+ 'Comma-separated list of strings (that will be returned in the response body in the previously defined `statusJsonPath` field) that will be considered as SUCCESS status.',
+ 'pipeline.config.webhook.canceledStatuses':
+ 'Comma-separated list of strings (that will be returned in the response body in the previously defined `statusJsonPath` field) that will be considered as CANCELED status.',
+ 'pipeline.config.webhook.terminalStatuses':
+ 'Comma-separated list of strings (that will be returned in the response body in the previously defined `statusJsonPath` field) that will be considered as TERMINAL status.',
+ 'pipeline.config.webhook.customHeaders': 'Key-value pairs to be sent as additional headers to the service.',
+ 'pipeline.config.webhook.failFastCodes':
+ 'Comma-separated HTTP status codes (4xx or 5xx) that will cause this webhook stage to fail without retrying.',
+ 'pipeline.config.webhook.signalCancellation':
+ 'Trigger a specific webhook if this stage is cancelled by user or due to pipeline failure',
+ 'pipeline.config.parameter.label': '(Optional): a label to display when users are triggering the pipeline manually',
+ 'pipeline.config.parameter.description': `(Optional): if supplied, will be displayed to users as a tooltip
+ when triggering the pipeline manually. You can include HTML in this field.`,
+ 'pipeline.config.parameter.pinned': `(Optional): if checked, this parameter will be always shown in a pipeline execution view, otherwise it'll be collapsed by default.`,
+ 'pipeline.config.parameter.pinAll': `(Optional): if checked, all parameters will be shown in a pipeline execution view.`,
+ 'pipeline.config.failOnFailedExpressions': `When this option is enabled, the stage will be marked as failed if it contains any failed expressions`,
+ 'pipeline.config.roles.help': `
+ When the pipeline is triggered using an automated trigger, these roles will be used to decide if the pipeline has permissions to access a protected application or account.
+
+
+ To read from a protected application or account, the pipeline must have at least one role that has read access to the application or account.
+
+
+ To write to a protected application or account, the pipeline must have at least one role that has write access to the application or account.
+
+
+ Note: To prevent privilege escalation vulnerabilities, a user must be a member of all of the groups specified here in order to modify, and execute the pipeline.
`,
+ 'pipeline.config.entitytags.namespace': `All tags have an associated namespace (default will be used if unspecified) that provides a means of grouping tags by a logical owner.`,
+ 'pipeline.config.entitytags.value': `Value can either be a string or an object. If you want to use an object, input a valid JSON string.`,
+ 'pipeline.config.entitytags.region': `(Optional) Target a specific region, use * if you want to apply to all regions.`,
+ 'pipeline.config.deliveryConfig.manifest': `(Optional) Name of the file with your Delivery Config manifest. Leave blank to use the default name (${SETTINGS.managedDelivery?.defaultManifest} ).`,
+ 'pipeline.config.codebuild.source': `(Optional) Source of the build. It will be overridden to Spinnaker artifact if checked. If not checked, source configured in CodeBuild project will be used.`,
+ 'pipeline.config.codebuild.sourceType': `(Optional) Type of the source. It can be specified explicitly; otherwise, it will be inferred from source artifact.`,
+ 'pipeline.config.codebuild.sourceVersion': `(Optional) Source version of the build. If not specified, the artifact version will be used. If artifact doesn't have a version, the latest version will be used. See the CodeBuild reference for more information.`,
+ 'pipeline.config.codebuild.buildspec': `(Optional) Inline buildspec definition of the build. If not specified, buildspec configured in CodeBuild project will be used.`,
+ 'pipeline.config.codebuild.secondarySources': `(Optional) Secondary sources of the build. It can be overridden by adding Spinnaker Artifacts. If not specified, secondary sources configured in CodeBuild project will be used.`,
+ 'pipeline.config.codebuild.image': `(Optional) Image in which the build will run. It can be overridden by specifying the name of the image. If not specified, image configured in CodeBuild project will be used.`,
+ 'pipeline.config.codebuild.envVar': `(Optional) Environment variables that will be propagated into the build.`,
+};
+
+Object.keys(helpContents).forEach((key) => HelpContentsRegistry.register(key, helpContents[key]));
diff --git a/app/scripts/modules/core/src/pipeline/config/stages/manualJudgment/ManualJudgmentApproval.tsx b/app/scripts/modules/core/src/pipeline/config/stages/manualJudgment/ManualJudgmentApproval.tsx
new file mode 100644
index 00000000000..93f21f4c028
--- /dev/null
+++ b/app/scripts/modules/core/src/pipeline/config/stages/manualJudgment/ManualJudgmentApproval.tsx
@@ -0,0 +1,192 @@
+import React from 'react';
+import Select, { Option } from 'react-select';
+
+import { IExecution, IExecutionStage } from 'core/domain';
+import { Application } from 'core/application/application.model';
+import { Markdown } from 'core/presentation/Markdown';
+import { NgReact, ReactInjector } from 'core/reactShims';
+import { ApplicationReader } from 'core/application/service/ApplicationReader';
+import { AuthenticationService } from 'core/authentication';
+
+export interface IManualJudgmentApprovalProps {
+ execution: IExecution;
+ stage: IExecutionStage;
+ application: Application;
+}
+
+export interface IManualJudgmentApprovalState {
+ submitting: boolean;
+ judgmentDecision: string;
+ judgmentInput: { value?: string };
+ applicationRoles: { READ?: string[]; WRITE?: string[]; EXECUTE?: string[]; CREATE?: string[] };
+ userRoles: string[];
+ error: boolean;
+}
+
+export class ManualJudgmentApproval extends React.Component<
+ IManualJudgmentApprovalProps,
+ IManualJudgmentApprovalState
+> {
+ constructor(props: IManualJudgmentApprovalProps) {
+ super(props);
+ this.state = {
+ submitting: false,
+ judgmentDecision: null,
+ judgmentInput: {},
+ applicationRoles: {},
+ userRoles: [],
+ error: false,
+ };
+ }
+
+ public componentDidMount() {
+ const applicationName = this.props.execution.application;
+ ApplicationReader.getApplicationPermissions(applicationName).then(result => {
+ if (result) {
+ this.setState({
+ applicationRoles: result,
+ });
+ }
+ });
+ this.setState({
+ userRoles: AuthenticationService.getAuthenticatedUser().roles,
+ });
+ }
+
+ private provideJudgment(judgmentDecision: string): void {
+ const { application, execution, stage } = this.props;
+ const judgmentInput: string = this.state.judgmentInput ? this.state.judgmentInput.value : null;
+ this.setState({ submitting: true, error: false, judgmentDecision });
+ ReactInjector.manualJudgmentService.provideJudgment(application, execution, stage, judgmentDecision, judgmentInput);
+ }
+
+ private isManualJudgmentStageNotAuthorized(): boolean {
+ let isStageNotAuthorized = true;
+ let returnOnceFalse = true;
+ const {
+ applicationRoles,
+ userRoles
+ } = this.state;
+ const stageRoles = this.props.stage?.context?.selectedStageRoles || [];
+ if (!stageRoles.length) {
+ isStageNotAuthorized = false;
+ return isStageNotAuthorized;
+ }
+ const {
+ CREATE,
+ EXECUTE,
+ WRITE
+ } = applicationRoles;
+ userRoles.forEach(userRole => {
+ if (returnOnceFalse) {
+ if (stageRoles.includes(userRole)) {
+ isStageNotAuthorized = (WRITE || []).includes(userRole) ||
+ (EXECUTE || []).includes(userRole) ||
+ (CREATE || []).includes(userRole);
+ if (isStageNotAuthorized) {
+ isStageNotAuthorized = false;
+ returnOnceFalse = false;
+ } else {
+ isStageNotAuthorized = true;
+ }
+ }
+ }
+ })
+ return isStageNotAuthorized;
+ }
+
+ private isSubmitting(decision: string): boolean {
+ return (
+ this.props.stage.context.judgmentStatus === decision ||
+ (this.state.submitting && this.state.judgmentDecision === decision)
+ );
+ }
+
+ private handleJudgementChanged = (option: Option): void => {
+ this.setState({ judgmentInput: { value: option.value as string } });
+ };
+
+ private handleContinueClick = (): void => {
+ this.provideJudgment('continue');
+ };
+
+ private handleStopClick = (): void => {
+ this.provideJudgment('stop');
+ };
+
+ public render(): React.ReactElement {
+ const stage: IExecutionStage = this.props.stage;
+ const status: string = stage.status;
+
+ const options: Option[] = (stage.context.judgmentInputs || []).map((o: { value: string }) => {
+ return { value: o.value, label: o.value };
+ });
+
+ const showOptions =
+ !['SKIPPED', 'SUCCEEDED'].includes(status) && (!stage.context.judgmentStatus || status === 'RUNNING');
+
+ const hasInstructions = !!stage.context.instructions;
+ const { ButtonBusyIndicator } = NgReact;
+
+ return (
+
+ {hasInstructions && (
+
+ )}
+ {showOptions && (
+
+ {options.length > 0 && (
+
+ )}
+
+
+ {this.isSubmitting('stop') && }
+ {stage.context.stopButtonLabel || 'Stop'}
+
+
+ {this.isSubmitting('continue') && }
+ {stage.context.continueButtonLabel || 'Continue'}
+
+
+
+ )}
+ {this.state.error && (
+
There was an error recording your decision. Please try again.
+ )}
+
+ );
+ }
+}
diff --git a/app/scripts/modules/core/src/pipeline/config/stages/stage.html b/app/scripts/modules/core/src/pipeline/config/stages/stage.html
new file mode 100644
index 00000000000..4e39f4c955a
--- /dev/null
+++ b/app/scripts/modules/core/src/pipeline/config/stages/stage.html
@@ -0,0 +1,163 @@
+
diff --git a/app/scripts/modules/core/src/pipeline/config/stages/stage.module.js b/app/scripts/modules/core/src/pipeline/config/stages/stage.module.js
new file mode 100644
index 00000000000..72dd93bd25e
--- /dev/null
+++ b/app/scripts/modules/core/src/pipeline/config/stages/stage.module.js
@@ -0,0 +1,393 @@
+'use strict';
+
+import { module } from 'angular';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { defaultsDeep, extend, omit } from 'lodash';
+
+import { AccountService } from 'core/account/AccountService';
+import { API } from 'core/api';
+import { BASE_EXECUTION_DETAILS_CTRL } from './common/baseExecutionDetails.controller';
+import { ConfirmationModalService } from 'core/confirmationModal';
+import { STAGE_NAME } from './StageName';
+import { PipelineConfigService } from '../services/PipelineConfigService';
+import { Registry } from 'core/registry';
+import { StageConfigWrapper } from './StageConfigWrapper';
+import { EditStageJsonModal } from './common/EditStageJsonModal';
+import { ReactModal } from 'core/presentation';
+import { PRODUCES_ARTIFACTS_REACT } from './producesArtifacts/ProducesArtifacts';
+import { OVERRRIDE_FAILURE } from './overrideFailure/overrideFailure.module';
+import { OVERRIDE_TIMEOUT_COMPONENT } from './overrideTimeout/overrideTimeout.module';
+import { ApplicationReader } from 'core/application/service/ApplicationReader';
+import { CORE_PIPELINE_CONFIG_STAGES_OPTIONALSTAGE_OPTIONALSTAGE_DIRECTIVE } from './optionalStage/optionalStage.directive';
+import { CORE_PIPELINE_CONFIG_STAGES_FAILONFAILEDEXPRESSIONS_FAILONFAILEDEXPRESSIONS_DIRECTIVE } from './failOnFailedExpressions/failOnFailedExpressions.directive';
+import { CORE_PIPELINE_CONFIG_STAGES_COMMON_STAGECONFIGFIELD_STAGECONFIGFIELD_DIRECTIVE } from './common/stageConfigField/stageConfigField.directive';
+
+export const CORE_PIPELINE_CONFIG_STAGES_STAGE_MODULE = 'spinnaker.core.pipeline.config.stage';
+export const name = CORE_PIPELINE_CONFIG_STAGES_STAGE_MODULE; // for backwards compatibility
+module(CORE_PIPELINE_CONFIG_STAGES_STAGE_MODULE, [
+ PRODUCES_ARTIFACTS_REACT,
+ BASE_EXECUTION_DETAILS_CTRL,
+ STAGE_NAME,
+ OVERRIDE_TIMEOUT_COMPONENT,
+ OVERRRIDE_FAILURE,
+ CORE_PIPELINE_CONFIG_STAGES_OPTIONALSTAGE_OPTIONALSTAGE_DIRECTIVE,
+ CORE_PIPELINE_CONFIG_STAGES_FAILONFAILEDEXPRESSIONS_FAILONFAILEDEXPRESSIONS_DIRECTIVE,
+ CORE_PIPELINE_CONFIG_STAGES_COMMON_STAGECONFIGFIELD_STAGECONFIGFIELD_DIRECTIVE,
+])
+ .directive('pipelineConfigStage', function () {
+ return {
+ restrict: 'E',
+ require: '^pipelineConfigurer',
+ scope: {
+ viewState: '=',
+ application: '=',
+ pipeline: '=',
+ stageFieldUpdated: '<',
+ },
+ controller: 'StageConfigCtrl as stageConfigCtrl',
+ templateUrl: require('./stage.html'),
+ link: function (scope, elem, attrs, pipelineConfigurerCtrl) {
+ scope.pipelineConfigurerCtrl = pipelineConfigurerCtrl;
+ },
+ };
+ })
+ .controller('StageConfigCtrl', [
+ '$scope',
+ '$element',
+ '$compile',
+ '$controller',
+ '$templateCache',
+ function ($scope, $element, $compile, $controller, $templateCache) {
+ let lastStageScope, reactComponentMounted;
+ let appPermissions = {};
+ let appRoles = [];
+ $scope.options = {
+ stageTypes: [],
+ selectedStageType: null,
+ stageRoles: [],
+ };
+
+ AccountService.applicationAccounts($scope.application).then((accounts) => {
+ $scope.options.stageTypes = Registry.pipeline.getConfigurableStageTypes(accounts);
+ $scope.showProviders = new Set(accounts.map((a) => a.cloudProvider)).size > 1;
+ });
+
+ if ($scope.pipeline.strategy) {
+ $scope.options.stageTypes = $scope.options.stageTypes.filter((stageType) => {
+ return stageType.strategy || false;
+ });
+ }
+
+ function getConfig(stage) {
+ return Registry.pipeline.getStageConfig(stage);
+ }
+
+ $scope.groupDependencyOptions = function (stage) {
+ const requisiteStageRefIds = $scope.stage.requisiteStageRefIds || [];
+ return stage.available
+ ? 'Available'
+ : requisiteStageRefIds.includes(stage.refId)
+ ? null
+ : 'Downstream dependencies (unavailable)';
+ };
+
+ $scope.stageProducesArtifacts = function () {
+ if (!$scope.stage) {
+ return false;
+ }
+
+ const stageConfig = Registry.pipeline.getStageConfig($scope.stage);
+
+ if (!stageConfig) {
+ return false;
+ } else {
+ return !!stageConfig.producesArtifacts;
+ }
+ };
+
+ $scope.producesArtifactsChanged = function (artifacts) {
+ $scope.$applyAsync(() => {
+ $scope.stage.expectedArtifacts = artifacts;
+ });
+ };
+
+ $scope.updateAvailableDependencyStages = function () {
+ const availableDependencyStages = PipelineConfigService.getDependencyCandidateStages(
+ $scope.pipeline,
+ $scope.stage,
+ );
+ $scope.options.dependencies = availableDependencyStages.map(function (stage) {
+ return {
+ name: stage.name,
+ refId: stage.refId,
+ available: true,
+ };
+ });
+
+ $scope.pipeline.stages.forEach(function (stage) {
+ if (stage !== $scope.stage && !availableDependencyStages.includes(stage)) {
+ $scope.options.dependencies.push({
+ name: stage.name,
+ refId: stage.refId,
+ });
+ }
+ });
+ };
+
+ $scope.getApplicationPermissions = function() {
+ ApplicationReader.getApplicationPermissions($scope.application.name).then(result => {
+ appPermissions = result;
+ if (appPermissions) {
+ const readArray = appPermissions.READ || [];
+ const writeArray = appPermissions.WRITE || [];
+ const executeArray = appPermissions.EXECUTE || [];
+ appRoles = _.union(readArray, writeArray, executeArray);
+ appRoles = Array.from(new Set(appRoles));
+ $scope.updateAvailableStageRoles();
+ }
+ });
+ };
+
+ $scope.updateAvailableStageRoles = function() {
+ $scope.options.stageRoles = appRoles.map(function(value, index) {
+ return {
+ name: value,
+ roleId: value,
+ id: index,
+ available: true,
+ };
+ });
+ };
+
+ this.editStageJson = () => {
+ const modalProps = { dialogClassName: 'modal-lg modal-fullscreen' };
+ ReactModal.show(EditStageJsonModal, { stage: $scope.stage }, modalProps)
+ .then(() => {
+ $scope.$applyAsync(() => $scope.$broadcast('pipeline-json-edited'));
+ })
+ .catch(() => {}); // user closed modal
+ };
+
+ this.selectStageType = (stage) => {
+ $scope.stage.type = stage.key;
+ this.selectStage();
+ // clear stage-specific fields
+ Object.keys($scope.stage).forEach((k) => {
+ if (!['requisiteStageRefIds', 'refId', 'isNew', 'name', 'type'].includes(k)) {
+ delete $scope.stage[k];
+ }
+ });
+ };
+
+ this.updateStageField = (changes) => {
+ extend($scope.stage, changes);
+ $scope.stageFieldUpdated();
+ };
+
+ this.selectStage = function (newVal, oldVal) {
+ const stageDetailsNode = $element.find('.stage-details').get(0);
+ if ($scope.viewState.stageIndex >= $scope.pipeline.stages.length) {
+ $scope.viewState.stageIndex = $scope.pipeline.stages.length - 1;
+ }
+ $scope.stage = $scope.pipeline.stages[$scope.viewState.stageIndex];
+
+ if (!$scope.stage) {
+ return;
+ }
+
+ if (!$scope.stage.type) {
+ $scope.options.selectedStageType = null;
+ } else {
+ $scope.options.selectedStageType = $scope.stage.type;
+ }
+
+ $scope.updateAvailableDependencyStages();
+ const type = $scope.stage.type;
+ const stageScope = $scope.$new();
+
+ // clear existing contents
+ if (reactComponentMounted) {
+ ReactDOM.unmountComponentAtNode(stageDetailsNode);
+ reactComponentMounted = false;
+ } else {
+ $element.find('.stage-details').html('');
+ }
+
+ $scope.description = '';
+ if (lastStageScope) {
+ lastStageScope.$destroy();
+ }
+ $scope.extendedDescription = '';
+ lastStageScope = stageScope;
+ $scope.$on('$destroy', function () {
+ stageScope.$destroy();
+ });
+
+ if (type && stageDetailsNode) {
+ const config = getConfig($scope.stage);
+ if (config) {
+ $scope.canConfigureNotifications = !$scope.pipeline.strategy && !config.disableNotifications;
+ $scope.description = config.description;
+ $scope.extendedDescription = config.extendedDescription;
+ $scope.label = config.label;
+ if (config.addAliasToConfig) {
+ $scope.stage.alias = config.alias;
+ }
+ if (config.defaults) {
+ defaultsDeep($scope.stage, config.defaults);
+ }
+ if (config.useBaseProvider || config.provides) {
+ config.component = null;
+ config.templateUrl = require('./baseProviderStage/baseProviderStage.html');
+ config.controller = 'BaseProviderStageCtrl as baseProviderStageCtrl';
+ }
+ updateStageName(config, oldVal);
+ applyConfigController(config, stageScope);
+
+ const props = {
+ application: $scope.application,
+ stageFieldUpdated: $scope.stageFieldUpdated,
+ updateStageField: (changes) => {
+ // Reserved fields should not be mutated from with a StageConfig component
+ const allowedChanges = omit(changes, ['requisiteStageRefIds', 'refId', 'isNew', 'name', 'type']);
+
+ extend($scope.stage, allowedChanges);
+ $scope.stageFieldUpdated();
+ },
+ pipeline: $scope.pipeline,
+ stage: $scope.stage,
+ component: config.component,
+ configuration: config.configuration,
+ };
+ if (config.useBaseProvider || config.provides) {
+ stageScope.reactPropsForBaseProviderStage = props;
+ }
+ if (config.component) {
+ ReactDOM.render(React.createElement(StageConfigWrapper, props), stageDetailsNode);
+ } else {
+ const template = $templateCache.get(config.templateUrl);
+ const templateBody = $compile(template)(stageScope);
+ $element.find('.stage-details').html(templateBody);
+ }
+ reactComponentMounted = !!config.component;
+ }
+ } else {
+ $scope.label = null;
+ $scope.description = null;
+ $scope.extendedDescription = null;
+ }
+
+ updateStageConfig($scope.stage);
+ };
+
+ function applyConfigController(config, stageScope) {
+ if (config.controller) {
+ const ctrl = config.controller.split(' as ');
+ const controller = $controller(ctrl[0], {
+ $scope: stageScope,
+ stage: $scope.stage,
+ viewState: $scope.viewState,
+ });
+ if (ctrl.length === 2) {
+ stageScope[ctrl[1]] = controller;
+ }
+ if (config.controllerAs) {
+ stageScope[config.controllerAs] = controller;
+ }
+ }
+ }
+
+ function updateStageName(config, oldVal) {
+ // apply a default name if the type changes and the user has not specified a name
+ if (oldVal) {
+ const oldConfig = getConfig({ type: oldVal });
+ if (oldConfig && $scope.stage.name === oldConfig.label) {
+ $scope.stage.name = config.label;
+ }
+ }
+ if (!$scope.stage.name && config.label) {
+ $scope.stage.name = config.label;
+ }
+ }
+
+ function updateStageConfig(stage) {
+ $scope.$applyAsync(() => {
+ $scope.stageConfig = getConfig(stage);
+ });
+ }
+
+ this.updateNotifications = function (notifications) {
+ $scope.$applyAsync(() => {
+ $scope.stage.notifications = notifications;
+ });
+ };
+
+ this.handleSendNotificationsChanged = function () {
+ $scope.$applyAsync(() => {
+ $scope.stage.sendNotifications = !$scope.stage.sendNotifications;
+ });
+ };
+
+ $scope.getApplicationPermissions();
+ $scope.$on('pipeline-reverted', this.selectStage);
+ $scope.$on('pipeline-json-edited', this.selectStage);
+ $scope.$watch('stage.type', this.selectStage);
+ $scope.$watch('viewState.stageIndex', this.selectStage);
+ $scope.$watch('stage.refId', this.selectStage);
+ },
+ ])
+ .controller('RestartStageCtrl', [
+ '$scope',
+ '$stateParams',
+ function ($scope, $stateParams) {
+ const restartStage = function () {
+ return API.one('pipelines')
+ .one($stateParams.executionId)
+ .one('stages', $scope.stage.id)
+ .one('restart')
+ .data({ skip: false })
+ .put()
+ .then(function () {
+ $scope.stage.isRestarting = true;
+ });
+ };
+
+ this.restart = function () {
+ let body = null;
+ if ($scope.execution.isRunning) {
+ body =
+ 'This pipeline is currently running - restarting this stage will result in multiple concurrently running pipelines.
';
+ }
+ ConfirmationModalService.confirm({
+ header: 'Really restart ' + $scope.stage.name + '?',
+ buttonText: 'Restart ' + $scope.stage.name,
+ body: body,
+ submitMethod: restartStage,
+ });
+ };
+ },
+ ])
+ .filter('stageTypeMatch', () => {
+ return (stageTypes, search) => {
+ const q = search.toLowerCase();
+ return stageTypes
+ .filter((s) => s.label.toLowerCase().includes(q) || s.description.toLowerCase().includes(q))
+ .sort((a, b) => {
+ const aLabel = a.label.toLowerCase();
+ const bLabel = b.label.toLowerCase();
+ const aDescription = a.description.toLowerCase();
+ const bDescription = b.description.toLowerCase();
+ if (aLabel.includes(q) && !bLabel.includes(q)) {
+ return -1;
+ }
+ if (!aLabel.includes(q) && bLabel.includes(q)) {
+ return 1;
+ }
+ if (aLabel.includes(q) && bLabel.includes(q)) {
+ return aLabel.indexOf(q) - bLabel.indexOf(q);
+ }
+ return aDescription.indexOf(q) - bDescription.indexOf(q);
+ });
+ };
+ });
From 6e8f28bccbe1670daa5c6d33523c4be751aa04fb Mon Sep 17 00:00:00 2001
From: Nagendra
Date: Tue, 18 Apr 2023 12:44:34 +0530
Subject: [PATCH 14/38] no account case error handler while creation
---
packages/core/src/application/search/Applications.tsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/packages/core/src/application/search/Applications.tsx b/packages/core/src/application/search/Applications.tsx
index fc87a9687eb..07d7cdee4b1 100644
--- a/packages/core/src/application/search/Applications.tsx
+++ b/packages/core/src/application/search/Applications.tsx
@@ -171,7 +171,9 @@ export class Applications extends React.Component<{}, IApplicationsState> {
private fixAccount(application: IApplicationSummary): IApplicationSummary {
if (application.accounts) {
- application.accounts = application.accounts.split(',').sort().join(', ');
+ if (!Array.isArray(application.accounts)) {
+ application.accounts = application.accounts.split(',').sort().join(', ');
+ }
}
return application;
}
From 6276e01f1c9ef03f562b5a7e3cf9239d771e9ece Mon Sep 17 00:00:00 2001
From: Yugandharkumar
Date: Fri, 23 Jun 2023 12:41:25 +0530
Subject: [PATCH 15/38] Update deck-oes.yml
---
.github/workflows/deck-oes.yml | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/.github/workflows/deck-oes.yml b/.github/workflows/deck-oes.yml
index 170d3b3a07a..b62a291d900 100644
--- a/.github/workflows/deck-oes.yml
+++ b/.github/workflows/deck-oes.yml
@@ -4,7 +4,7 @@ on:
workflow_call:
push:
branches:
- - OES-1.30.x-master-adhoc
+ - OES-1.30.x-synch
env:
GRADLE_OPTS: -Dorg.gradle.daemon=false -Xmx6g -Xms6g
@@ -25,7 +25,6 @@ jobs:
with:
java-version: 11
distribution: 'zulu'
- cache: 'gradle'
- name: Prepare build variables
id: build_variables
run: |
From bc3843e10224ac5495f5cee91b863213f5646fa9 Mon Sep 17 00:00:00 2001
From: Spinnaker Bot <87720302+spinnakerbot2@users.noreply.github.com>
Date: Mon, 13 Feb 2023 23:07:28 -1000
Subject: [PATCH 16/38] Publish packages to NPM (#9946)
* chore(publish): publish packages (898a59504ea58a456baf19e0a21a4cace683feb7)
- deck-app@2.4.2
- @spinnaker/ecs@0.0.356
* feat(peerdep-sync): Synchronize peerdependencies
* chore(publish): publish peerdeps (898a59504ea58a456baf19e0a21a4cace683feb7)
- @spinnaker/pluginsdk-peerdeps@0.8.0
---------
Co-authored-by: spinnakerbot
---
packages/app/CHANGELOG.md | 8 ++++++++
packages/app/package.json | 4 ++--
packages/ecs/CHANGELOG.md | 11 +++++++++++
packages/ecs/package.json | 2 +-
packages/pluginsdk-peerdeps/CHANGELOG.md | 11 +++++++++++
packages/pluginsdk-peerdeps/package.json | 2 +-
6 files changed, 34 insertions(+), 4 deletions(-)
diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md
index 8c39ada411d..78250b2e125 100644
--- a/packages/app/CHANGELOG.md
+++ b/packages/app/CHANGELOG.md
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [2.4.2](https://github.com/spinnaker/deck/compare/deck-app@2.4.1...deck-app@2.4.2) (2023-02-07)
+
+**Note:** Version bump only for package deck-app
+
+
+
+
+
## [2.4.1](https://github.com/spinnaker/deck/compare/deck-app@2.4.0...deck-app@2.4.1) (2023-02-02)
diff --git a/packages/app/package.json b/packages/app/package.json
index b3cc160464e..0dec11164e4 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -1,7 +1,7 @@
{
"name": "deck-app",
"private": true,
- "version": "2.4.1",
+ "version": "2.4.2",
"description": "",
"main": "index.js",
"scripts": {
@@ -19,7 +19,7 @@
"@spinnaker/cloudfoundry": "^0.1.3",
"@spinnaker/core": "^0.23.0",
"@spinnaker/docker": "^0.0.137",
- "@spinnaker/ecs": "^0.0.355",
+ "@spinnaker/ecs": "^0.0.356",
"@spinnaker/google": "^0.2.3",
"@spinnaker/kayenta": "2.0.0",
"@spinnaker/kubernetes": "^0.4.0",
diff --git a/packages/ecs/CHANGELOG.md b/packages/ecs/CHANGELOG.md
index 008ce682de4..cb5e3e8899d 100644
--- a/packages/ecs/CHANGELOG.md
+++ b/packages/ecs/CHANGELOG.md
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [0.0.356](https://github.com/spinnaker/deck/compare/@spinnaker/ecs@0.0.355...@spinnaker/ecs@0.0.356) (2023-02-07)
+
+
+### Bug Fixes
+
+* **ecs:** skip checking for upstream stages for ECS deploy ([#9945](https://github.com/spinnaker/deck/issues/9945)) ([898a595](https://github.com/spinnaker/deck/commit/898a59504ea58a456baf19e0a21a4cace683feb7))
+
+
+
+
+
## [0.0.355](https://github.com/spinnaker/deck/compare/@spinnaker/ecs@0.0.354...@spinnaker/ecs@0.0.355) (2023-02-02)
**Note:** Version bump only for package @spinnaker/ecs
diff --git a/packages/ecs/package.json b/packages/ecs/package.json
index 41f589e13dd..eb01e5a8230 100644
--- a/packages/ecs/package.json
+++ b/packages/ecs/package.json
@@ -1,7 +1,7 @@
{
"name": "@spinnaker/ecs",
"license": "Apache-2.0",
- "version": "0.0.355",
+ "version": "0.0.356",
"module": "dist/index.js",
"typings": "dist/index.d.ts",
"scripts": {
diff --git a/packages/pluginsdk-peerdeps/CHANGELOG.md b/packages/pluginsdk-peerdeps/CHANGELOG.md
index 58e9acd407b..ebb508c9625 100644
--- a/packages/pluginsdk-peerdeps/CHANGELOG.md
+++ b/packages/pluginsdk-peerdeps/CHANGELOG.md
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [0.8.0](https://github.com/spinnaker/deck/compare/@spinnaker/pluginsdk-peerdeps@0.7.0...@spinnaker/pluginsdk-peerdeps@0.8.0) (2023-02-07)
+
+
+### Features
+
+* **peerdep-sync:** Synchronize peerdependencies ([51ba791](https://github.com/spinnaker/deck/commit/51ba7916af758d8197aac5ae827b55e2d9b7bd00))
+
+
+
+
+
# [0.7.0](https://github.com/spinnaker/deck/compare/@spinnaker/pluginsdk-peerdeps@0.6.0...@spinnaker/pluginsdk-peerdeps@0.7.0) (2023-02-02)
diff --git a/packages/pluginsdk-peerdeps/package.json b/packages/pluginsdk-peerdeps/package.json
index c55077e190c..6fe7d0eb59f 100644
--- a/packages/pluginsdk-peerdeps/package.json
+++ b/packages/pluginsdk-peerdeps/package.json
@@ -1,7 +1,7 @@
{
"name": "@spinnaker/pluginsdk-peerdeps",
"description": "Provides package dependencies to plugin developers",
- "version": "0.7.0",
+ "version": "0.8.0",
"license": "Apache-2.0",
"scripts": {
"temp": "./convert-peerdeps.js --from-peerdeps --input package.json --output package.temp.json",
From 53ae18ae4cd4b512fe6246ed53ea27f0d4023c89 Mon Sep 17 00:00:00 2001
From: SusmithaGundu <93190458+SusmithaGundu@users.noreply.github.com>
Date: Mon, 20 Feb 2023 17:16:21 +0530
Subject: [PATCH 17/38] fix(google): Resolved typo errors in GCE Autoscaler
feature (#9947)
---
.../components/metricSettings/metricSettings.component.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/google/src/autoscalingPolicy/components/metricSettings/metricSettings.component.html b/packages/google/src/autoscalingPolicy/components/metricSettings/metricSettings.component.html
index 1109cabdf61..7dcf282bdf3 100644
--- a/packages/google/src/autoscalingPolicy/components/metricSettings/metricSettings.component.html
+++ b/packages/google/src/autoscalingPolicy/components/metricSettings/metricSettings.component.html
@@ -113,7 +113,7 @@
@@ -151,7 +151,7 @@
name="singleInstanceAssignment"
class="form-control input-sm"
required
- ng-model="custom.singleInstaceAssignment"
+ ng-model="custom.singleInstanceAssignment"
min="0"
/>
From e2e3919632c451a1ebeebac07ddc30ae535a1c5d Mon Sep 17 00:00:00 2001
From: Spinnaker Bot <87720302+spinnakerbot2@users.noreply.github.com>
Date: Mon, 20 Feb 2023 03:28:14 -1000
Subject: [PATCH 18/38] Publish packages to NPM (#9949)
* chore(publish): publish packages (2250a477dd51cfd3695e28ad04e8c8466b5bcfe2)
- deck-app@2.4.3
- @spinnaker/google@0.2.4
* feat(peerdep-sync): Synchronize peerdependencies
* chore(publish): publish peerdeps (2250a477dd51cfd3695e28ad04e8c8466b5bcfe2)
- @spinnaker/pluginsdk-peerdeps@0.9.0
---------
Co-authored-by: spinnakerbot
---
packages/app/CHANGELOG.md | 8 ++++++++
packages/app/package.json | 4 ++--
packages/google/CHANGELOG.md | 11 +++++++++++
packages/google/package.json | 2 +-
packages/pluginsdk-peerdeps/CHANGELOG.md | 11 +++++++++++
packages/pluginsdk-peerdeps/package.json | 2 +-
6 files changed, 34 insertions(+), 4 deletions(-)
diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md
index 78250b2e125..27723834bdf 100644
--- a/packages/app/CHANGELOG.md
+++ b/packages/app/CHANGELOG.md
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [2.4.3](https://github.com/spinnaker/deck/compare/deck-app@2.4.2...deck-app@2.4.3) (2023-02-20)
+
+**Note:** Version bump only for package deck-app
+
+
+
+
+
## [2.4.2](https://github.com/spinnaker/deck/compare/deck-app@2.4.1...deck-app@2.4.2) (2023-02-07)
**Note:** Version bump only for package deck-app
diff --git a/packages/app/package.json b/packages/app/package.json
index 0dec11164e4..0ce56b9134b 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -1,7 +1,7 @@
{
"name": "deck-app",
"private": true,
- "version": "2.4.2",
+ "version": "2.4.3",
"description": "",
"main": "index.js",
"scripts": {
@@ -20,7 +20,7 @@
"@spinnaker/core": "^0.23.0",
"@spinnaker/docker": "^0.0.137",
"@spinnaker/ecs": "^0.0.356",
- "@spinnaker/google": "^0.2.3",
+ "@spinnaker/google": "^0.2.4",
"@spinnaker/kayenta": "2.0.0",
"@spinnaker/kubernetes": "^0.4.0",
"@spinnaker/oracle": "^0.0.81",
diff --git a/packages/google/CHANGELOG.md b/packages/google/CHANGELOG.md
index e1a7b5fb97b..3ce16460d00 100644
--- a/packages/google/CHANGELOG.md
+++ b/packages/google/CHANGELOG.md
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [0.2.4](https://github.com/spinnaker/deck/compare/@spinnaker/google@0.2.3...@spinnaker/google@0.2.4) (2023-02-20)
+
+
+### Bug Fixes
+
+* **google:** Resolved typo errors in GCE Autoscaler feature ([#9947](https://github.com/spinnaker/deck/issues/9947)) ([2250a47](https://github.com/spinnaker/deck/commit/2250a477dd51cfd3695e28ad04e8c8466b5bcfe2))
+
+
+
+
+
## [0.2.3](https://github.com/spinnaker/deck/compare/@spinnaker/google@0.2.2...@spinnaker/google@0.2.3) (2023-02-01)
**Note:** Version bump only for package @spinnaker/google
diff --git a/packages/google/package.json b/packages/google/package.json
index c23e38b1a92..cb458cf2ef9 100644
--- a/packages/google/package.json
+++ b/packages/google/package.json
@@ -1,7 +1,7 @@
{
"name": "@spinnaker/google",
"license": "Apache-2.0",
- "version": "0.2.3",
+ "version": "0.2.4",
"module": "dist/index.js",
"typings": "dist/index.d.ts",
"scripts": {
diff --git a/packages/pluginsdk-peerdeps/CHANGELOG.md b/packages/pluginsdk-peerdeps/CHANGELOG.md
index ebb508c9625..76664d66b07 100644
--- a/packages/pluginsdk-peerdeps/CHANGELOG.md
+++ b/packages/pluginsdk-peerdeps/CHANGELOG.md
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [0.9.0](https://github.com/spinnaker/deck/compare/@spinnaker/pluginsdk-peerdeps@0.8.0...@spinnaker/pluginsdk-peerdeps@0.9.0) (2023-02-20)
+
+
+### Features
+
+* **peerdep-sync:** Synchronize peerdependencies ([474b4de](https://github.com/spinnaker/deck/commit/474b4defd99202122b5920eefafd1dbb055bccae))
+
+
+
+
+
# [0.8.0](https://github.com/spinnaker/deck/compare/@spinnaker/pluginsdk-peerdeps@0.7.0...@spinnaker/pluginsdk-peerdeps@0.8.0) (2023-02-07)
diff --git a/packages/pluginsdk-peerdeps/package.json b/packages/pluginsdk-peerdeps/package.json
index 6fe7d0eb59f..930a4a38d37 100644
--- a/packages/pluginsdk-peerdeps/package.json
+++ b/packages/pluginsdk-peerdeps/package.json
@@ -1,7 +1,7 @@
{
"name": "@spinnaker/pluginsdk-peerdeps",
"description": "Provides package dependencies to plugin developers",
- "version": "0.8.0",
+ "version": "0.9.0",
"license": "Apache-2.0",
"scripts": {
"temp": "./convert-peerdeps.js --from-peerdeps --input package.json --output package.temp.json",
From 5db12f57c359fbd74fd977d75ec0e0ea6b08ec71 Mon Sep 17 00:00:00 2001
From: Rajinderpal Singh Siddhu
<61963157+siddhu-opsmx@users.noreply.github.com>
Date: Thu, 2 Mar 2023 16:37:25 +0530
Subject: [PATCH 19/38] fix(6755): Resolved issue regarding warning reporting
when cloning a server group in AWS (#9948)
---
.../common/v2instanceArchetypeSelector.component.ts | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/packages/core/src/serverGroup/configure/common/v2instanceArchetypeSelector.component.ts b/packages/core/src/serverGroup/configure/common/v2instanceArchetypeSelector.component.ts
index f68f1e668a5..9954a3500f8 100644
--- a/packages/core/src/serverGroup/configure/common/v2instanceArchetypeSelector.component.ts
+++ b/packages/core/src/serverGroup/configure/common/v2instanceArchetypeSelector.component.ts
@@ -22,6 +22,16 @@ class V2InstanceArchetypeSelectorController implements IComponentController {
const { $scope } = this;
this.instanceTypeService.getCategories(this.command.selectedProvider).then((categories) => {
$scope.instanceProfiles = categories;
+ // Resetting the 'unavailable' properties to avoid caching initially.
+ categories.forEach((profile) => {
+ if (profile.type === this.command.viewState.instanceProfile) {
+ profile.families.forEach((family) => {
+ family.instanceTypes.forEach((instanceType) => {
+ instanceType.unavailable = false;
+ });
+ });
+ }
+ });
if ($scope.instanceProfiles.length % 3 === 0) {
$scope.columns = 3;
}
From 2eb6993e71fe1f6d441807340b699f317a4f7d57 Mon Sep 17 00:00:00 2001
From: Cameron Motevasselani
Date: Wed, 8 Mar 2023 12:27:16 -0800
Subject: [PATCH 20/38] Revert "feat(Blue/Green): Add warning label and enhance
disable/enable manifest stage with Deployment kind." (#9951)
---
.../DeployManifestStageForm.tsx | 1 -
.../ManifestDeploymentOptions.spec.tsx | 15 -------------
.../ManifestDeploymentOptions.tsx | 21 +------------------
.../traffic/ManifestTrafficStageConfig.tsx | 2 +-
4 files changed, 2 insertions(+), 37 deletions(-)
diff --git a/packages/kubernetes/src/pipelines/stages/deployManifest/DeployManifestStageForm.tsx b/packages/kubernetes/src/pipelines/stages/deployManifest/DeployManifestStageForm.tsx
index 1cd1125564c..c818185097d 100644
--- a/packages/kubernetes/src/pipelines/stages/deployManifest/DeployManifestStageForm.tsx
+++ b/packages/kubernetes/src/pipelines/stages/deployManifest/DeployManifestStageForm.tsx
@@ -192,7 +192,6 @@ export class DeployManifestStageForm extends React.Component<
config={stage.trafficManagement}
onConfigChange={(config) => this.props.formik.setFieldValue('trafficManagement', config)}
selectedAccount={stage.account}
- isDeploymentKind={this.state.rawManifest.includes('kind: Deployment')}
/>
);
diff --git a/packages/kubernetes/src/pipelines/stages/deployManifest/ManifestDeploymentOptions.spec.tsx b/packages/kubernetes/src/pipelines/stages/deployManifest/ManifestDeploymentOptions.spec.tsx
index 85a24bf8e6e..b0b7883abb0 100644
--- a/packages/kubernetes/src/pipelines/stages/deployManifest/ManifestDeploymentOptions.spec.tsx
+++ b/packages/kubernetes/src/pipelines/stages/deployManifest/ManifestDeploymentOptions.spec.tsx
@@ -17,7 +17,6 @@ describe(' ', () => {
config: defaultTrafficManagementConfig,
onConfigChange: onConfigChangeSpy,
selectedAccount: null,
- isDeploymentKind: null,
};
wrapper = shallow( );
});
@@ -72,19 +71,5 @@ describe(' ', () => {
wrapper = shallow( );
expect(wrapper.find('p[id="redBlackWarning"]').exists()).toEqual(true);
});
-
- it('strategy bluegreen with deployment kind should display warning label', () => {
- props.config.options.strategy = 'bluegreen';
- props.isDeploymentKind = true;
- wrapper = shallow( );
- expect(wrapper.find('p[id="blueGreenWarning"]').exists()).toEqual(true);
- });
-
- it('strategy bluegreen with replicaSet kind should not display warning label', () => {
- props.config.options.strategy = 'bluegreen';
- props.isDeploymentKind = false;
- wrapper = shallow( );
- expect(wrapper.find('p[id="blueGreenWarning"]').exists()).toEqual(false);
- });
});
});
diff --git a/packages/kubernetes/src/pipelines/stages/deployManifest/ManifestDeploymentOptions.tsx b/packages/kubernetes/src/pipelines/stages/deployManifest/ManifestDeploymentOptions.tsx
index 1bdb99b7fb9..625f46eb597 100644
--- a/packages/kubernetes/src/pipelines/stages/deployManifest/ManifestDeploymentOptions.tsx
+++ b/packages/kubernetes/src/pipelines/stages/deployManifest/ManifestDeploymentOptions.tsx
@@ -37,24 +37,18 @@ export interface IManifestDeploymentOptionsProps {
config: ITrafficManagementConfig;
onConfigChange: (config: ITrafficManagementConfig) => void;
selectedAccount: string;
- isDeploymentKind: boolean;
}
export interface IManifestDeploymentOptionsState {
services: string[];
showRedBlackWarningMessage: boolean;
- showBlueGreenDeploymentWarningMessage: boolean;
}
export class ManifestDeploymentOptions extends React.Component<
IManifestDeploymentOptionsProps,
IManifestDeploymentOptionsState
> {
- public state: IManifestDeploymentOptionsState = {
- services: [],
- showRedBlackWarningMessage: false,
- showBlueGreenDeploymentWarningMessage: false,
- };
+ public state: IManifestDeploymentOptionsState = { services: [], showRedBlackWarningMessage: false };
private onConfigChange = (key: string, value: any): void => {
this.setState({ showRedBlackWarningMessage: false });
@@ -62,9 +56,6 @@ export class ManifestDeploymentOptions extends React.Component<
value = 'bluegreen';
this.setState({ showRedBlackWarningMessage: true });
}
- if (value === 'bluegreen' && this.props.isDeploymentKind) {
- this.setState({ showBlueGreenDeploymentWarningMessage: true });
- }
this.updateProps(key, value);
};
@@ -113,14 +104,10 @@ export class ManifestDeploymentOptions extends React.Component<
public componentDidMount() {
this.fetchServices();
this.setState({ showRedBlackWarningMessage: false });
- this.setState({ showBlueGreenDeploymentWarningMessage: false });
if (this.props.config.options.strategy === 'redblack') {
this.setState({ showRedBlackWarningMessage: true });
this.updateProps('options.strategy', 'bluegreen');
}
- if (this.props.config.options.strategy === 'bluegreen' && this.props.isDeploymentKind) {
- this.setState({ showBlueGreenDeploymentWarningMessage: true });
- }
}
public componentDidUpdate(prevProps: IManifestDeploymentOptionsProps) {
@@ -141,7 +128,6 @@ export class ManifestDeploymentOptions extends React.Component<
public render() {
const { config } = this.props;
const { showRedBlackWarningMessage } = this.state;
- const { showBlueGreenDeploymentWarningMessage } = this.state;
return (
<>
Rollout Strategy Options
@@ -208,11 +194,6 @@ export class ManifestDeploymentOptions extends React.Component<
blue/green instead!
)}
- {showBlueGreenDeploymentWarningMessage && (
-
- Warning: Blue/Green strategy may cause downtime for Deployment kind!
-
- )}
>
)}
diff --git a/packages/kubernetes/src/pipelines/stages/traffic/ManifestTrafficStageConfig.tsx b/packages/kubernetes/src/pipelines/stages/traffic/ManifestTrafficStageConfig.tsx
index bb98be4cd79..331a72b8a3a 100644
--- a/packages/kubernetes/src/pipelines/stages/traffic/ManifestTrafficStageConfig.tsx
+++ b/packages/kubernetes/src/pipelines/stages/traffic/ManifestTrafficStageConfig.tsx
@@ -36,7 +36,7 @@ export class ManifestTrafficStageConfig extends React.Component
);
From 90de1a4750d7e641b565431a1c36c9c71449efe3 Mon Sep 17 00:00:00 2001
From: Cameron Motevasselani
Date: Thu, 9 Mar 2023 15:10:23 -0800
Subject: [PATCH 21/38] chore(dev): specifying React code for Deck PRs (#9953)
Co-authored-by: Cameron Motevasselani
---
README.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/README.md b/README.md
index 56e29bce7ea..c3d98e050de 100644
--- a/README.md
+++ b/README.md
@@ -39,6 +39,10 @@ The following external resources can be specified with environment variables:
For example, `API_HOST=http://spinnaker.prod.netflix.net yarn start` will run Deck with `http://spinnaker.prod.netflix.net` as the API host.
+## Development
+
+Deck has a combination of Angular and React, but is moving to React only. New changes made to the Deck project should use React wherever possible.
+
## Testing
To run the tests within the application, run `yarn test`.
From d168e6bc6c89ce483e3c2e6751740feddbf14214 Mon Sep 17 00:00:00 2001
From: Rajinderpal Singh Siddhu
<61963157+siddhu-opsmx@users.noreply.github.com>
Date: Fri, 10 Mar 2023 17:31:32 +0530
Subject: [PATCH 22/38] feat(provider/cloudrun): Added cloudrun functionality
to deck (#9931)
* feat(provider/cloudrun): Added cloudrun functionality to deck
* feat(provider/cloudrun): Incorporated Suggested Changes
* feat(provider/cloudRun): Fixed build issue
---------
Co-authored-by: rsinghsidhu
---
halconfig/settings.js | 6 +
karma-shim.js | 3 +
karma.conf.js | 2 +
package.json | 1 +
packages/app/package.json | 1 +
packages/app/src/app.ts | 2 +
packages/app/src/settings.js | 6 +
packages/cloudrun/.npmignore | 4 +
packages/cloudrun/package.json | 53 ++++
packages/cloudrun/src/cloudrun.module.ts | 72 +++++
packages/cloudrun/src/cloudrun.settings.ts | 14 +
.../cloudrun/src/common/cloudrunHealth.ts | 3 +
.../common/componentUrlDetails.component.ts | 22 ++
...onditionalDescriptionListItem.component.ts | 37 +++
.../src/common/domain/ICloudrunInstance.ts | 21 ++
.../common/domain/ICloudrunLoadBalancer.ts | 18 ++
packages/cloudrun/src/common/domain/index.ts | 2 +
.../common/loadBalancerMessage.component.html | 11 +
.../common/loadBalancerMessage.component.ts | 13 +
packages/cloudrun/src/help/cloudrun.help.ts | 67 +++++
packages/cloudrun/src/index.ts | 3 +
.../instance/details/details.controller.ts | 84 ++++++
.../src/instance/details/details.html | 68 +++++
packages/cloudrun/src/interfaces/index.ts | 1 +
.../src/interfaces/infrastructure.types.ts | 23 ++
.../allocationConfigurationRow.component.ts | 70 +++++
.../wizard/basicSettings.component.html | 43 +++
.../wizard/basicSettings.component.ts | 86 ++++++
...eAllocationConfigurationRow.component.html | 27 ++
...ageAllocationConfigurationRow.component.ts | 79 ++++++
.../configure/wizard/wizard.controller.ts | 157 +++++++++++
.../loadBalancer/configure/wizard/wizard.html | 39 +++
.../loadBalancer/configure/wizard/wizard.less | 9 +
.../details/details.controller.ts | 140 ++++++++++
.../src/loadBalancer/details/details.html | 86 ++++++
packages/cloudrun/src/loadBalancer/index.ts | 3 +
.../loadBalancer/loadBalancerTransformer.ts | 168 ++++++++++++
packages/cloudrun/src/logo/cloudrun.icon.svg | 27 ++
packages/cloudrun/src/logo/cloudrun.logo.less | 6 +
packages/cloudrun/src/logo/cloudrun.logo.png | Bin 0 -> 1338 bytes
.../cloudrun/src/pipeline/pipeline.module.ts | 6 +
.../cloudrunEditLoadBalancerStage.ts | 74 +++++
.../editLoadBalancerExecutionDetails.html | 33 +++
.../editLoadBalancerStage.html | 51 ++++
.../loadBalancerChoice.modal.controller.ts | 65 +++++
.../loadBalancerChoice.modal.html | 53 ++++
.../serverGroupCommandBuilder.service.ts | 254 ++++++++++++++++++
.../configure/wizard/BasicSettings.tsx | 160 +++++++++++
.../configure/wizard/ConfigFiles.tsx | 75 ++++++
.../configure/wizard/serverGroupWizard.tsx | 150 +++++++++++
.../serverGroup/details/details.controller.ts | 134 +++++++++
.../src/serverGroup/details/details.html | 94 +++++++
packages/cloudrun/src/serverGroup/index.ts | 3 +
.../serverGroupTransformer.service.ts | 61 +++++
packages/cloudrun/tsconfig.json | 10 +
scripts/buildModules.js | 1 +
scripts/build_order.sh | 1 +
scripts/bumpPackage.js | 1 +
tsconfig.json | 5 +-
59 files changed, 2707 insertions(+), 1 deletion(-)
create mode 100644 packages/cloudrun/.npmignore
create mode 100644 packages/cloudrun/package.json
create mode 100644 packages/cloudrun/src/cloudrun.module.ts
create mode 100644 packages/cloudrun/src/cloudrun.settings.ts
create mode 100644 packages/cloudrun/src/common/cloudrunHealth.ts
create mode 100644 packages/cloudrun/src/common/componentUrlDetails.component.ts
create mode 100644 packages/cloudrun/src/common/conditionalDescriptionListItem.component.ts
create mode 100644 packages/cloudrun/src/common/domain/ICloudrunInstance.ts
create mode 100644 packages/cloudrun/src/common/domain/ICloudrunLoadBalancer.ts
create mode 100644 packages/cloudrun/src/common/domain/index.ts
create mode 100644 packages/cloudrun/src/common/loadBalancerMessage.component.html
create mode 100644 packages/cloudrun/src/common/loadBalancerMessage.component.ts
create mode 100644 packages/cloudrun/src/help/cloudrun.help.ts
create mode 100644 packages/cloudrun/src/index.ts
create mode 100644 packages/cloudrun/src/instance/details/details.controller.ts
create mode 100644 packages/cloudrun/src/instance/details/details.html
create mode 100644 packages/cloudrun/src/interfaces/index.ts
create mode 100644 packages/cloudrun/src/interfaces/infrastructure.types.ts
create mode 100644 packages/cloudrun/src/loadBalancer/configure/wizard/allocationConfigurationRow.component.ts
create mode 100644 packages/cloudrun/src/loadBalancer/configure/wizard/basicSettings.component.html
create mode 100644 packages/cloudrun/src/loadBalancer/configure/wizard/basicSettings.component.ts
create mode 100644 packages/cloudrun/src/loadBalancer/configure/wizard/stageAllocationConfigurationRow.component.html
create mode 100644 packages/cloudrun/src/loadBalancer/configure/wizard/stageAllocationConfigurationRow.component.ts
create mode 100644 packages/cloudrun/src/loadBalancer/configure/wizard/wizard.controller.ts
create mode 100644 packages/cloudrun/src/loadBalancer/configure/wizard/wizard.html
create mode 100644 packages/cloudrun/src/loadBalancer/configure/wizard/wizard.less
create mode 100644 packages/cloudrun/src/loadBalancer/details/details.controller.ts
create mode 100644 packages/cloudrun/src/loadBalancer/details/details.html
create mode 100644 packages/cloudrun/src/loadBalancer/index.ts
create mode 100644 packages/cloudrun/src/loadBalancer/loadBalancerTransformer.ts
create mode 100644 packages/cloudrun/src/logo/cloudrun.icon.svg
create mode 100644 packages/cloudrun/src/logo/cloudrun.logo.less
create mode 100644 packages/cloudrun/src/logo/cloudrun.logo.png
create mode 100644 packages/cloudrun/src/pipeline/pipeline.module.ts
create mode 100644 packages/cloudrun/src/pipeline/stages/editLoadBalancer/cloudrunEditLoadBalancerStage.ts
create mode 100644 packages/cloudrun/src/pipeline/stages/editLoadBalancer/editLoadBalancerExecutionDetails.html
create mode 100644 packages/cloudrun/src/pipeline/stages/editLoadBalancer/editLoadBalancerStage.html
create mode 100644 packages/cloudrun/src/pipeline/stages/editLoadBalancer/loadBalancerChoice.modal.controller.ts
create mode 100644 packages/cloudrun/src/pipeline/stages/editLoadBalancer/loadBalancerChoice.modal.html
create mode 100644 packages/cloudrun/src/serverGroup/configure/serverGroupCommandBuilder.service.ts
create mode 100644 packages/cloudrun/src/serverGroup/configure/wizard/BasicSettings.tsx
create mode 100644 packages/cloudrun/src/serverGroup/configure/wizard/ConfigFiles.tsx
create mode 100644 packages/cloudrun/src/serverGroup/configure/wizard/serverGroupWizard.tsx
create mode 100644 packages/cloudrun/src/serverGroup/details/details.controller.ts
create mode 100644 packages/cloudrun/src/serverGroup/details/details.html
create mode 100644 packages/cloudrun/src/serverGroup/index.ts
create mode 100644 packages/cloudrun/src/serverGroup/serverGroupTransformer.service.ts
create mode 100644 packages/cloudrun/tsconfig.json
diff --git a/halconfig/settings.js b/halconfig/settings.js
index 039ea00f95c..3e3d6aaf303 100644
--- a/halconfig/settings.js
+++ b/halconfig/settings.js
@@ -63,6 +63,11 @@ var cloudfoundry = {
account: '{%cloudfoundry.default.account%}',
},
};
+var cloudrun = {
+ defaults: {
+ account: '{%cloudrun.default.account%}',
+ },
+};
var dcos = {
defaults: {
account: '{%dcos.default.account%}',
@@ -145,6 +150,7 @@ window.spinnakerSettings = {
aws: aws,
azure: azure,
cloudfoundry: cloudfoundry,
+ cloudrun: cloudrun,
dcos: dcos,
ecs: ecs,
gce: gce,
diff --git a/karma-shim.js b/karma-shim.js
index 6aa2110839b..c0172403488 100644
--- a/karma-shim.js
+++ b/karma-shim.js
@@ -34,6 +34,9 @@ testContext.keys().forEach(testContext);
testContext = require.context('./packages/cloudfoundry/src', true, /\.spec\.(js|ts|tsx)$/);
testContext.keys().forEach(testContext);
+testContext = require.context('./packages/cloudrun/src', true, /\.spec\.(js|ts|tsx)$/);
+testContext.keys().forEach(testContext);
+
testContext = require.context('./packages/core/src', true, /\.spec\.(js|ts|tsx)$/);
testContext.keys().forEach(testContext);
diff --git a/karma.conf.js b/karma.conf.js
index c5252fd14bd..1e00dae0485 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -32,6 +32,8 @@ const webpackConfig = {
'@spinnaker/azure': path.resolve(`${MODULES_ROOT}/azure/src`),
cloudfoundry: path.resolve(`${MODULES_ROOT}/cloudfoundry/src`),
'@spinnaker/cloudfoundry': path.resolve(`${MODULES_ROOT}/cloudfoundry/src`),
+ cloudrun: path.resolve(`${MODULES_ROOT}/cloudrun/src`),
+ '@spinnaker/cloudrun': path.resolve(`${MODULES_ROOT}/cloudrun/src`),
core: path.resolve(`${MODULES_ROOT}/core/src`),
'@spinnaker/core': path.resolve(`${MODULES_ROOT}/core/src`),
dcos: path.resolve(`${MODULES_ROOT}/dcos/src`),
diff --git a/package.json b/package.json
index 652749cef8b..189c3f8b7bf 100644
--- a/package.json
+++ b/package.json
@@ -86,6 +86,7 @@
"packages/appengine",
"packages/azure",
"packages/cloudfoundry",
+ "packages/cloudrun",
"packages/core",
"packages/dcos",
"packages/docker",
diff --git a/packages/app/package.json b/packages/app/package.json
index 0ce56b9134b..63b502f7f63 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -17,6 +17,7 @@
"@spinnaker/amazon": "^0.13.5",
"@spinnaker/appengine": "^0.1.3",
"@spinnaker/cloudfoundry": "^0.1.3",
+ "@spinnaker/cloudrun": "^0.0.1",
"@spinnaker/core": "^0.23.0",
"@spinnaker/docker": "^0.0.137",
"@spinnaker/ecs": "^0.0.356",
diff --git a/packages/app/src/app.ts b/packages/app/src/app.ts
index 86c6d8fa28d..788fa85bb2e 100644
--- a/packages/app/src/app.ts
+++ b/packages/app/src/app.ts
@@ -15,6 +15,7 @@ import { ORACLE_MODULE } from '@spinnaker/oracle';
import { KAYENTA_MODULE } from '@spinnaker/kayenta';
import { TITUS_MODULE } from '@spinnaker/titus';
import { ECS_MODULE } from '@spinnaker/ecs';
+import { CLOUDRUN_MODULE } from '@spinnaker/cloudrun';
import '@spinnaker/cloudfoundry';
module('netflix.spinnaker', [
@@ -30,4 +31,5 @@ module('netflix.spinnaker', [
KUBERNETES_MODULE,
KAYENTA_MODULE,
TITUS_MODULE,
+ CLOUDRUN_MODULE,
]);
diff --git a/packages/app/src/settings.js b/packages/app/src/settings.js
index 6683223489c..555c7a55434 100644
--- a/packages/app/src/settings.js
+++ b/packages/app/src/settings.js
@@ -91,6 +91,7 @@ window.spinnakerSettings = {
'aws',
'azure',
'cloudfoundry',
+ 'cloudrun',
'dcos',
'ecs',
'gce',
@@ -211,6 +212,11 @@ window.spinnakerSettings = {
account: 'my-cloudfoundry-account',
},
},
+ cloudrun: {
+ defaults: {
+ account: 'my-cloudrun-account',
+ },
+ },
dcos: {
defaults: {
account: 'my-dcos-account',
diff --git a/packages/cloudrun/.npmignore b/packages/cloudrun/.npmignore
new file mode 100644
index 00000000000..7879f9c4b4a
--- /dev/null
+++ b/packages/cloudrun/.npmignore
@@ -0,0 +1,4 @@
+yalc.*
+.*
+tsconfig.json
+webpack.config.js
diff --git a/packages/cloudrun/package.json b/packages/cloudrun/package.json
new file mode 100644
index 00000000000..ca1d181e31e
--- /dev/null
+++ b/packages/cloudrun/package.json
@@ -0,0 +1,53 @@
+{
+ "name": "@spinnaker/cloudrun",
+ "license": "Apache-2.0",
+ "version": "0.0.1",
+ "module": "dist/index.js",
+ "typings": "dist/index.d.ts",
+ "scripts": {
+ "clean": "shx rm -rf dist",
+ "prepublishOnly": "npm run build",
+ "build": "npm run clean && spinnaker-scripts build",
+ "dev": "spinnaker-scripts start",
+ "dev:push": "spinnaker-scripts start --push",
+ "lib": "npm run build"
+ },
+ "dependencies": {
+ "@spinnaker/core": "^0.23.0",
+ "@uirouter/angularjs": "1.0.26",
+ "@uirouter/react": "1.0.7",
+ "angular": "1.6.10",
+ "angular-ui-bootstrap": "2.5.0",
+ "brace": "0.11.1",
+ "dompurify": "^2.3.10",
+ "enzyme": "3.11.0",
+ "formik": "1.5.1",
+ "js-yaml": "3.13.1",
+ "lodash": "4.17.21",
+ "luxon": "1.23.0",
+ "ngimport": "0.6.1",
+ "react": "16.14.0",
+ "react-ace": "6.4.0",
+ "react-bootstrap": "0.32.1",
+ "react-ga": "2.4.1",
+ "react-select": "1.2.1",
+ "react2angular": "3.2.1",
+ "rxjs": "6.6.7"
+ },
+ "devDependencies": {
+ "@spinnaker/eslint-plugin": "^3.0.1",
+ "@spinnaker/scripts": "^0.3.0",
+ "@types/angular": "1.6.26",
+ "@types/angular-ui-bootstrap": "0.13.41",
+ "@types/dompurify": "^2.3.3",
+ "@types/enzyme": "3.10.3",
+ "@types/js-yaml": "3.5.30",
+ "@types/lodash": "4.14.64",
+ "@types/luxon": "1.11.1",
+ "@types/react": "16.14.10",
+ "@types/react-bootstrap": "0.32.5",
+ "@types/react-select": "1.3.4",
+ "shx": "0.3.3",
+ "typescript": "4.3.5"
+ }
+}
diff --git a/packages/cloudrun/src/cloudrun.module.ts b/packages/cloudrun/src/cloudrun.module.ts
new file mode 100644
index 00000000000..4443a4ed27a
--- /dev/null
+++ b/packages/cloudrun/src/cloudrun.module.ts
@@ -0,0 +1,72 @@
+import { module } from 'angular';
+
+import { CloudProviderRegistry, DeploymentStrategyRegistry } from '@spinnaker/core';
+
+import { CLOUDRUN_COMPONENT_URL_DETAILS } from './common/componentUrlDetails.component';
+import { CLOUDRUN_LOAD_BALANCER_CREATE_MESSAGE } from './common/loadBalancerMessage.component';
+import './help/cloudrun.help';
+import { CLOUDRUN_INSTANCE_DETAILS_CTRL } from './instance/details/details.controller';
+import { CLOUDRUN_ALLOCATION_CONFIGURATION_ROW } from './loadBalancer/configure/wizard/allocationConfigurationRow.component';
+import { CLOUDRUN_LOAD_BALANCER_BASIC_SETTINGS } from './loadBalancer/configure/wizard/basicSettings.component';
+import { CLOUDRUN_STAGE_ALLOCATION_CONFIGURATION_ROW } from './loadBalancer/configure/wizard/stageAllocationConfigurationRow.component';
+import { CLOUDRUN_LOAD_BALANCER_WIZARD_CTRL } from './loadBalancer/configure/wizard/wizard.controller';
+import { CLOUDRUN_LOAD_BALANCER_DETAILS_CTRL } from './loadBalancer/details/details.controller';
+import { CLOUDRUN_LOAD_BALANCER_TRANSFORMER } from './loadBalancer/loadBalancerTransformer';
+import logo from './logo/cloudrun.logo.png';
+import { CLOUDRUN_PIPELINE_MODULE } from './pipeline/pipeline.module';
+import { CLOUDRUN_SERVER_GROUP_COMMAND_BUILDER } from './serverGroup/configure/serverGroupCommandBuilder.service';
+import { ServerGroupWizard } from './serverGroup/configure/wizard/serverGroupWizard';
+import { CLOUDRUN_SERVER_GROUP_DETAILS_CTRL } from './serverGroup/details/details.controller';
+import { CLOUDRUN_SERVER_GROUP_TRANSFORMER } from './serverGroup/serverGroupTransformer.service';
+
+import './logo/cloudrun.logo.less';
+
+export const CLOUDRUN_MODULE = 'spinnaker.cloudrun';
+
+const requires = [
+ CLOUDRUN_COMPONENT_URL_DETAILS,
+ CLOUDRUN_SERVER_GROUP_COMMAND_BUILDER,
+ CLOUDRUN_SERVER_GROUP_DETAILS_CTRL,
+ CLOUDRUN_SERVER_GROUP_TRANSFORMER,
+ CLOUDRUN_LOAD_BALANCER_TRANSFORMER,
+ CLOUDRUN_LOAD_BALANCER_DETAILS_CTRL,
+ CLOUDRUN_LOAD_BALANCER_WIZARD_CTRL,
+ CLOUDRUN_LOAD_BALANCER_CREATE_MESSAGE,
+ CLOUDRUN_ALLOCATION_CONFIGURATION_ROW,
+ CLOUDRUN_LOAD_BALANCER_BASIC_SETTINGS,
+ CLOUDRUN_STAGE_ALLOCATION_CONFIGURATION_ROW,
+ CLOUDRUN_PIPELINE_MODULE,
+ CLOUDRUN_INSTANCE_DETAILS_CTRL,
+];
+
+module(CLOUDRUN_MODULE, requires).config(() => {
+ CloudProviderRegistry.registerProvider('cloudrun', {
+ name: 'cloudrun',
+ logo: {
+ path: logo,
+ },
+
+ instance: {
+ detailsTemplateUrl: require('./instance/details/details.html'),
+ detailsController: 'cloudrunInstanceDetailsCtrl',
+ },
+ serverGroup: {
+ CloneServerGroupModal: ServerGroupWizard,
+ commandBuilder: 'cloudrunV2ServerGroupCommandBuilder',
+ detailsController: 'cloudrunV2ServerGroupDetailsCtrl',
+ detailsTemplateUrl: require('./serverGroup/details/details.html'),
+ transformer: 'cloudrunV2ServerGroupTransformer',
+ skipUpstreamStageCheck: true,
+ },
+
+ loadBalancer: {
+ transformer: 'cloudrunLoadBalancerTransformer',
+ createLoadBalancerTemplateUrl: require('./loadBalancer/configure/wizard/wizard.html'),
+ createLoadBalancerController: 'cloudrunLoadBalancerWizardCtrl',
+ detailsTemplateUrl: require('./loadBalancer/details/details.html'),
+ detailsController: 'cloudrunLoadBalancerDetailsCtrl',
+ },
+ });
+});
+
+DeploymentStrategyRegistry.registerProvider('cloudrun', ['custom', 'redblack', 'rollingpush', 'rollingredblack']);
diff --git a/packages/cloudrun/src/cloudrun.settings.ts b/packages/cloudrun/src/cloudrun.settings.ts
new file mode 100644
index 00000000000..f3b8cc0b080
--- /dev/null
+++ b/packages/cloudrun/src/cloudrun.settings.ts
@@ -0,0 +1,14 @@
+import type { IProviderSettings } from '@spinnaker/core';
+import { SETTINGS } from '@spinnaker/core';
+
+export interface ICloudrunProviderSettings extends IProviderSettings {
+ defaults: {
+ account?: string;
+ };
+}
+
+export const CloudrunProviderSettings: ICloudrunProviderSettings = (SETTINGS.providers
+ .cloudrun as ICloudrunProviderSettings) || { defaults: {} };
+if (CloudrunProviderSettings) {
+ CloudrunProviderSettings.resetToOriginal = SETTINGS.resetProvider('cloudrun');
+}
diff --git a/packages/cloudrun/src/common/cloudrunHealth.ts b/packages/cloudrun/src/common/cloudrunHealth.ts
new file mode 100644
index 00000000000..5dfc89243af
--- /dev/null
+++ b/packages/cloudrun/src/common/cloudrunHealth.ts
@@ -0,0 +1,3 @@
+export class CloudrunHealth {
+ public static PLATFORM = 'Cloud Run Service';
+}
diff --git a/packages/cloudrun/src/common/componentUrlDetails.component.ts b/packages/cloudrun/src/common/componentUrlDetails.component.ts
new file mode 100644
index 00000000000..3fdf0e43e58
--- /dev/null
+++ b/packages/cloudrun/src/common/componentUrlDetails.component.ts
@@ -0,0 +1,22 @@
+import type { IComponentOptions } from 'angular';
+import { module } from 'angular';
+
+const cloudrunComponentUrlDetailsComponent: IComponentOptions = {
+ bindings: { component: '<' },
+ template: `
+ HTTPS
+
+ {{$ctrl.component.url}}
+
+
+ `,
+};
+
+export const CLOUDRUN_COMPONENT_URL_DETAILS = 'spinnaker.cloudrun.componentUrlDetails.component';
+
+module(CLOUDRUN_COMPONENT_URL_DETAILS, []).component(
+ 'cloudrunComponentUrlDetails',
+ cloudrunComponentUrlDetailsComponent,
+);
diff --git a/packages/cloudrun/src/common/conditionalDescriptionListItem.component.ts b/packages/cloudrun/src/common/conditionalDescriptionListItem.component.ts
new file mode 100644
index 00000000000..59f72a77066
--- /dev/null
+++ b/packages/cloudrun/src/common/conditionalDescriptionListItem.component.ts
@@ -0,0 +1,37 @@
+import type { IComponentOptions, IController, IFilterService } from 'angular';
+import { module } from 'angular';
+
+class CloudrunConditionalDescriptionListItemCtrl implements IController {
+ public label: string;
+ public key: string;
+ public component: any;
+
+ public static $inject = ['$filter'];
+ constructor(private $filter: IFilterService) {}
+
+ public $onInit(): void {
+ if (!this.label) {
+ this.label = this.$filter('robotToHuman')(this.key);
+ }
+ }
+}
+
+const cloudrunConditionalDescriptionListItem: IComponentOptions = {
+ bindings: { label: '@', key: '@', component: '<' },
+ transclude: {
+ keyLabel: '?keyText',
+ valueLabel: '?valueLabel',
+ },
+ template: `
+ {{$ctrl.label}}
+ {{$ctrl.component[$ctrl.key]}}
+ `,
+ controller: CloudrunConditionalDescriptionListItemCtrl,
+};
+
+export const CLOUDRUN_CONDITIONAL_DESCRIPTION_LIST_ITEM = 'spinnaker.cloudrun.conditionalDescriptionListItem';
+
+module(CLOUDRUN_CONDITIONAL_DESCRIPTION_LIST_ITEM, []).component(
+ 'cloudrunConditionalDtDd',
+ cloudrunConditionalDescriptionListItem,
+);
diff --git a/packages/cloudrun/src/common/domain/ICloudrunInstance.ts b/packages/cloudrun/src/common/domain/ICloudrunInstance.ts
new file mode 100644
index 00000000000..d2805b27fca
--- /dev/null
+++ b/packages/cloudrun/src/common/domain/ICloudrunInstance.ts
@@ -0,0 +1,21 @@
+import type { IInstance } from '@spinnaker/core';
+
+export interface ICloudrunInstance extends IInstance {
+ name: string;
+ id: string;
+ account?: string;
+ region?: string;
+ instanceStatus: 'DYNAMIC' | 'RESIDENT' | 'UNKNOWN';
+ launchTime: number;
+ loadBalancers: string[];
+ serverGroup: string;
+ vmDebugEnabled: boolean;
+ vmName: string;
+ vmStatus: string;
+ vmZoneName: string;
+ qps: number;
+ healthState: string;
+ cloudProvider: string;
+ errors: number;
+ averageLatency: number;
+}
diff --git a/packages/cloudrun/src/common/domain/ICloudrunLoadBalancer.ts b/packages/cloudrun/src/common/domain/ICloudrunLoadBalancer.ts
new file mode 100644
index 00000000000..a8845c5aef4
--- /dev/null
+++ b/packages/cloudrun/src/common/domain/ICloudrunLoadBalancer.ts
@@ -0,0 +1,18 @@
+import type { ILoadBalancer } from '@spinnaker/core';
+
+export interface ICloudrunLoadBalancer extends ILoadBalancer {
+ credentials?: string;
+ split?: ICloudrunTrafficSplit;
+ migrateTraffic: boolean;
+ dispatchRules?: ICloudrunDispatchRule[];
+}
+
+export interface ICloudrunTrafficSplit {
+ trafficTargets: [{ revisionName: string; percent: number }];
+}
+
+export interface ICloudrunDispatchRule {
+ domain: string;
+ path: string;
+ service: string;
+}
diff --git a/packages/cloudrun/src/common/domain/index.ts b/packages/cloudrun/src/common/domain/index.ts
new file mode 100644
index 00000000000..0f4f8e82793
--- /dev/null
+++ b/packages/cloudrun/src/common/domain/index.ts
@@ -0,0 +1,2 @@
+export * from './ICloudrunLoadBalancer';
+export * from './ICloudrunInstance';
diff --git a/packages/cloudrun/src/common/loadBalancerMessage.component.html b/packages/cloudrun/src/common/loadBalancerMessage.component.html
new file mode 100644
index 00000000000..b59d7f5555a
--- /dev/null
+++ b/packages/cloudrun/src/common/loadBalancerMessage.component.html
@@ -0,0 +1,11 @@
+
+
+
+
+ Spinnaker cannot create a load balancer for Cloud Run.
+ A Spinnaker load balancer maps to an Cloud Run Service, which will be created automatically alongside a
+ Revision. Once created, the Service can be edited as a Load Balancer.
+
+
+
+
diff --git a/packages/cloudrun/src/common/loadBalancerMessage.component.ts b/packages/cloudrun/src/common/loadBalancerMessage.component.ts
new file mode 100644
index 00000000000..c0d14e6a5f1
--- /dev/null
+++ b/packages/cloudrun/src/common/loadBalancerMessage.component.ts
@@ -0,0 +1,13 @@
+import { module } from 'angular';
+
+const cloudRunLoadBalancerMessageComponent: ng.IComponentOptions = {
+ bindings: { showCreateMessage: '<', columnOffset: '@', columns: '@' },
+ templateUrl: require('./loadBalancerMessage.component.html'),
+};
+
+export const CLOUDRUN_LOAD_BALANCER_CREATE_MESSAGE = 'spinnaker.cloudrun.loadBalancer.createMessage.component';
+
+module(CLOUDRUN_LOAD_BALANCER_CREATE_MESSAGE, []).component(
+ 'cloudrunLoadBalancerMessage',
+ cloudRunLoadBalancerMessageComponent,
+);
diff --git a/packages/cloudrun/src/help/cloudrun.help.ts b/packages/cloudrun/src/help/cloudrun.help.ts
new file mode 100644
index 00000000000..2f4be46b1be
--- /dev/null
+++ b/packages/cloudrun/src/help/cloudrun.help.ts
@@ -0,0 +1,67 @@
+import { HelpContentsRegistry } from '@spinnaker/core';
+
+const helpContents = [
+ {
+ key: 'cloudrun.serverGroup.stack',
+ value:
+ '(Optional) Stack is one of the core naming components of a cluster, used to create vertical stacks of dependent services for integration testing.',
+ },
+
+ {
+ key: 'cloudrun.serverGroup.file',
+ value: `
+ apiVersion: serving.knative.dev/v1
+ kind: Service
+ metadata:
+ name: spinappcloud1
+ namespace: '135005621049'
+ labels:
+ cloud.googleapis.com/location: us-central1
+ annotations:
+ run.googleapis.com/client-name: cloud-console
+ serving.knative.dev/creator: kiran@opsmx.io
+ serving.knative.dev/lastModifier: kiran@opsmx.io
+ client.knative.dev/user-image: us-docker.pkg.dev/cloudrun/container/hello
+ run.googleapis.com/ingress-status: all
+ spec:
+ template:
+ metagoogleapis.com/ingress: all
+ run.data:
+ name: spinappcloud1
+ annotations:
+ run.googleapis.com/client-name: cloud-console
+ autoscaling.knative.dev/minScale: '1'
+ autoscaling.knative.dev/maxScale: '3'
+ spec:
+ containerConcurrency: 80
+ timeoutSeconds: 200
+ serviceAccountName:spinnaker-cloudrun-account@my-orbit-project-71824.iam.gserviceaccount.com
+ containers:
+ - image:us-docker.pkg.dev/cloudrun/container/hello
+ ports:
+ - name: http1
+ containerPort: 8080
+ resources:
+ limits:
+ cpu: 1000m
+ memory: 256Mi
+
+ `,
+ },
+
+ {
+ key: 'cloudrun.serverGroup.detail',
+ value:
+ ' (Optional) Detail is a string of free-form alphanumeric characters and hyphens to describe any other variables.',
+ },
+ {
+ key: 'cloudrun.serverGroup.configFiles',
+ value: ` The contents of a Cloud Run Service yaml
`,
+ },
+ {
+ key: 'cloudrun.loadBalancer.allocations',
+ value: 'An allocation is the percent of traffic directed to a server group.',
+ },
+];
+
+helpContents.forEach((entry) => HelpContentsRegistry.register(entry.key, entry.value));
diff --git a/packages/cloudrun/src/index.ts b/packages/cloudrun/src/index.ts
new file mode 100644
index 00000000000..e0595fbf9f4
--- /dev/null
+++ b/packages/cloudrun/src/index.ts
@@ -0,0 +1,3 @@
+export * from './cloudrun.module';
+export * from './serverGroup';
+export * from './loadBalancer';
diff --git a/packages/cloudrun/src/instance/details/details.controller.ts b/packages/cloudrun/src/instance/details/details.controller.ts
new file mode 100644
index 00000000000..d8fa5a8f555
--- /dev/null
+++ b/packages/cloudrun/src/instance/details/details.controller.ts
@@ -0,0 +1,84 @@
+import type { IController, IQService } from 'angular';
+import { module } from 'angular';
+import { flattenDeep } from 'lodash';
+
+import type { Application, ILoadBalancer } from '@spinnaker/core';
+import { InstanceReader, RecentHistoryService } from '@spinnaker/core';
+import type { ICloudrunInstance } from '../../common/domain';
+
+interface InstanceFromStateParams {
+ instanceId: string;
+}
+
+interface InstanceManager {
+ account: string;
+ region: string;
+ category: string; // e.g., serverGroup, loadBalancer.
+ name: string; // Parent resource name, not instance name.
+ instances: ICloudrunInstance[];
+}
+
+class CloudrunInstanceDetailsController implements IController {
+ public state = { loading: true };
+ public instance: ICloudrunInstance;
+ public instanceIdNotFound: string;
+ public upToolTip = "A Cloud Run instance is 'Up' if a load balancer is directing traffic to its server group.";
+ public outOfServiceToolTip = `
+ A Cloud Run instance is 'Out Of Service' if no load balancers are directing traffic to its server group.`;
+
+ public static $inject = ['$q', 'app', 'instance'];
+
+ constructor(private $q: IQService, private app: Application, instance: InstanceFromStateParams) {
+ this.app
+ .ready()
+ .then(() => this.retrieveInstance(instance))
+ .then((instanceDetails) => {
+ this.instance = instanceDetails;
+ this.state.loading = false;
+ })
+ .catch(() => {
+ this.instanceIdNotFound = instance.instanceId;
+ this.state.loading = false;
+ });
+ }
+
+ private retrieveInstance(instance: InstanceFromStateParams): PromiseLike {
+ const instanceLocatorPredicate = (dataSource: InstanceManager) => {
+ return dataSource.instances.some((possibleMatch) => possibleMatch.id === instance.instanceId);
+ };
+
+ const dataSources: InstanceManager[] = flattenDeep([
+ this.app.getDataSource('serverGroups').data,
+ this.app.getDataSource('loadBalancers').data,
+ this.app.getDataSource('loadBalancers').data.map((loadBalancer: ILoadBalancer) => loadBalancer.serverGroups),
+ ]);
+
+ const instanceManager = dataSources.find(instanceLocatorPredicate);
+
+ if (instanceManager) {
+ const recentHistoryExtraData: { [key: string]: string } = {
+ region: instanceManager.region,
+ account: instanceManager.account,
+ };
+ if (instanceManager.category === 'serverGroup') {
+ recentHistoryExtraData.serverGroup = instanceManager.name;
+ }
+ RecentHistoryService.addExtraDataToLatest('instances', recentHistoryExtraData);
+
+ return InstanceReader.getInstanceDetails(
+ instanceManager.account,
+ instanceManager.region,
+ instance.instanceId,
+ ).then((instanceDetails: ICloudrunInstance) => {
+ instanceDetails.account = instanceManager.account;
+ instanceDetails.region = instanceManager.region;
+ return instanceDetails;
+ });
+ } else {
+ return this.$q.reject();
+ }
+ }
+}
+
+export const CLOUDRUN_INSTANCE_DETAILS_CTRL = 'spinnaker.cloudrun.instanceDetails.controller';
+module(CLOUDRUN_INSTANCE_DETAILS_CTRL, []).controller('cloudrunInstanceDetailsCtrl', CloudrunInstanceDetailsController);
diff --git a/packages/cloudrun/src/instance/details/details.html b/packages/cloudrun/src/instance/details/details.html
new file mode 100644
index 00000000000..cd8117a3658
--- /dev/null
+++ b/packages/cloudrun/src/instance/details/details.html
@@ -0,0 +1,68 @@
+
+
+
+
+
+ Launched
+ {{ctrl.instance.launchTime | timestamp}}
+ In
+ {{}}
+ Server Group
+
+ {{ctrl.instance.serverGroup}}
+
+ Region
+ {{ctrl.instance.region}}
+
+
+
+
+
+ Load Balancer
+
+
+
+ {{ctrl.instance.loadBalancers[0]}}
+
+
+
+
+
+
+
+
+
Instance not found.
+
+
+
+
diff --git a/packages/cloudrun/src/interfaces/index.ts b/packages/cloudrun/src/interfaces/index.ts
new file mode 100644
index 00000000000..14605457055
--- /dev/null
+++ b/packages/cloudrun/src/interfaces/index.ts
@@ -0,0 +1 @@
+export * from './infrastructure.types';
diff --git a/packages/cloudrun/src/interfaces/infrastructure.types.ts b/packages/cloudrun/src/interfaces/infrastructure.types.ts
new file mode 100644
index 00000000000..a1afe582c6a
--- /dev/null
+++ b/packages/cloudrun/src/interfaces/infrastructure.types.ts
@@ -0,0 +1,23 @@
+import type { IInstance, ILoadBalancer, IMoniker, IServerGroup } from '@spinnaker/core';
+
+export interface ICloudrunResource {
+ apiVersion: string;
+ createdTime?: number;
+ displayName: string;
+ kind: string;
+ namespace: string;
+}
+
+export interface ICloudrunInstance extends IInstance, ICloudrunResource {
+ humanReadableName: string;
+ moniker: IMoniker;
+ publicDnsName?: string;
+}
+
+export interface ICloudrunLoadBalancer extends ILoadBalancer, ICloudrunResource {}
+
+export interface ICloudrunServerGroup extends IServerGroup, ICloudrunResource {
+ disabled: boolean;
+}
+
+//export interface ICloudrunServerGroupManager extends IServerGroupManager, ICloudrunResource {}
diff --git a/packages/cloudrun/src/loadBalancer/configure/wizard/allocationConfigurationRow.component.ts b/packages/cloudrun/src/loadBalancer/configure/wizard/allocationConfigurationRow.component.ts
new file mode 100644
index 00000000000..5189b4c47cc
--- /dev/null
+++ b/packages/cloudrun/src/loadBalancer/configure/wizard/allocationConfigurationRow.component.ts
@@ -0,0 +1,70 @@
+import type { IComponentOptions, IController } from 'angular';
+import { module } from 'angular';
+import { uniq } from 'lodash';
+
+import type { ICloudrunAllocationDescription } from '../../loadBalancerTransformer';
+
+class CloudrunAllocationConfigurationRowCtrl implements IController {
+ public allocationDescription: ICloudrunAllocationDescription;
+ public serverGroupOptions: string[];
+
+ public getServerGroupOptions(): string[] {
+ if (this.allocationDescription.revisionName) {
+ return uniq(this.serverGroupOptions.concat(this.allocationDescription.revisionName));
+ } else {
+ return this.serverGroupOptions;
+ }
+ }
+}
+
+const cloudrunAllocationConfigurationRowComponent: IComponentOptions = {
+ bindings: {
+ allocationDescription: '<',
+ removeAllocation: '&',
+ serverGroupOptions: '<',
+ onAllocationChange: '&',
+ },
+ template: `
+
+ `,
+ controller: CloudrunAllocationConfigurationRowCtrl,
+};
+
+export const CLOUDRUN_ALLOCATION_CONFIGURATION_ROW = 'spinnaker.cloudrun.allocationConfigurationRow.component';
+
+module(CLOUDRUN_ALLOCATION_CONFIGURATION_ROW, []).component(
+ 'cloudrunAllocationConfigurationRow',
+ cloudrunAllocationConfigurationRowComponent,
+);
diff --git a/packages/cloudrun/src/loadBalancer/configure/wizard/basicSettings.component.html b/packages/cloudrun/src/loadBalancer/configure/wizard/basicSettings.component.html
new file mode 100644
index 00000000000..97668ba57ef
--- /dev/null
+++ b/packages/cloudrun/src/loadBalancer/configure/wizard/basicSettings.component.html
@@ -0,0 +1,43 @@
+
+
+
diff --git a/packages/cloudrun/src/loadBalancer/configure/wizard/basicSettings.component.ts b/packages/cloudrun/src/loadBalancer/configure/wizard/basicSettings.component.ts
new file mode 100644
index 00000000000..afae927d82b
--- /dev/null
+++ b/packages/cloudrun/src/loadBalancer/configure/wizard/basicSettings.component.ts
@@ -0,0 +1,86 @@
+import type { IController } from 'angular';
+import { module } from 'angular';
+import { difference } from 'lodash';
+
+import type { CloudrunLoadBalancerUpsertDescription } from '../../loadBalancerTransformer';
+
+class CloudrunLoadBalancerSettingsController implements IController {
+ public loadBalancer: CloudrunLoadBalancerUpsertDescription;
+ public serverGroupOptions: string[];
+ public forPipelineConfig: boolean;
+
+ public $onInit(): void {
+ this.updateServerGroupOptions();
+ }
+
+ public addAllocation(): void {
+ const remainingServerGroups = this.serverGroupsWithoutAllocation();
+ if (remainingServerGroups.length) {
+ this.loadBalancer.splitDescription.allocationDescriptions.push({
+ revisionName: remainingServerGroups[0],
+ percent: 0,
+ });
+ this.updateServerGroupOptions();
+ } else if (this.forPipelineConfig) {
+ this.loadBalancer.splitDescription.allocationDescriptions.push({
+ percent: 0,
+ revisionName: '',
+ });
+ }
+ }
+
+ public removeAllocation(index: number): void {
+ this.loadBalancer.splitDescription.allocationDescriptions.splice(index, 1);
+ this.updateServerGroupOptions();
+ }
+
+ public allocationIsInvalid(): boolean {
+ return (
+ this.loadBalancer.splitDescription.allocationDescriptions.reduce(
+ (sum, allocationDescription) => sum + allocationDescription.percent,
+ 0,
+ ) !== 100
+ );
+ }
+
+ public updateServerGroupOptions(): void {
+ this.serverGroupOptions = this.serverGroupsWithoutAllocation();
+ }
+
+ public showAddButton(): boolean {
+ if (this.forPipelineConfig) {
+ return true;
+ } else {
+ return this.serverGroupsWithoutAllocation().length > 0;
+ }
+ }
+
+ public initializeAsTextInput(serverGroupName: string): boolean {
+ if (this.forPipelineConfig) {
+ return !this.loadBalancer.serverGroups.map((serverGroup) => serverGroup.name).includes(serverGroupName);
+ } else {
+ return false;
+ }
+ }
+
+ private serverGroupsWithoutAllocation(): string[] {
+ const serverGroupsWithAllocation = this.loadBalancer.splitDescription.allocationDescriptions.map(
+ (description) => description.revisionName,
+ );
+ const allServerGroups = this.loadBalancer.serverGroups.map((serverGroup) => serverGroup.name);
+ return difference(allServerGroups, serverGroupsWithAllocation);
+ }
+}
+
+const cloudrunLoadBalancerSettingsComponent: ng.IComponentOptions = {
+ bindings: { loadBalancer: '=', forPipelineConfig: '<', application: '<' },
+ controller: CloudrunLoadBalancerSettingsController,
+ templateUrl: require('./basicSettings.component.html'),
+};
+
+export const CLOUDRUN_LOAD_BALANCER_BASIC_SETTINGS = 'spinnaker.cloudrun.loadBalancerSettings.component';
+
+module(CLOUDRUN_LOAD_BALANCER_BASIC_SETTINGS, []).component(
+ 'cloudrunLoadBalancerBasicSettings',
+ cloudrunLoadBalancerSettingsComponent,
+);
diff --git a/packages/cloudrun/src/loadBalancer/configure/wizard/stageAllocationConfigurationRow.component.html b/packages/cloudrun/src/loadBalancer/configure/wizard/stageAllocationConfigurationRow.component.html
new file mode 100644
index 00000000000..46dea2edcd1
--- /dev/null
+++ b/packages/cloudrun/src/loadBalancer/configure/wizard/stageAllocationConfigurationRow.component.html
@@ -0,0 +1,27 @@
+
diff --git a/packages/cloudrun/src/loadBalancer/configure/wizard/stageAllocationConfigurationRow.component.ts b/packages/cloudrun/src/loadBalancer/configure/wizard/stageAllocationConfigurationRow.component.ts
new file mode 100644
index 00000000000..ce62882f2c2
--- /dev/null
+++ b/packages/cloudrun/src/loadBalancer/configure/wizard/stageAllocationConfigurationRow.component.ts
@@ -0,0 +1,79 @@
+import type { IComponentOptions, IController } from 'angular';
+import { module } from 'angular';
+import { uniq } from 'lodash';
+
+import type { Application } from '@spinnaker/core';
+import { AppListExtractor, StageConstants } from '@spinnaker/core';
+
+import type { ICloudrunAllocationDescription } from '../../loadBalancerTransformer';
+
+class CloudrunStageAllocationLabelCtrl implements IController {
+ public inputViewValue: string;
+ private allocationDescription: ICloudrunAllocationDescription;
+
+ public $doCheck(): void {
+ this.setInputViewValue();
+ }
+
+ private setInputViewValue(): void {
+ this.inputViewValue = this.allocationDescription.revisionName;
+ }
+}
+
+const cloudrunStageAllocationLabel: IComponentOptions = {
+ bindings: { allocationDescription: '<' },
+ controller: CloudrunStageAllocationLabelCtrl,
+ template: ` `,
+};
+
+class CloudrunStageAllocationConfigurationRowCtrl implements IController {
+ public allocationDescription: ICloudrunAllocationDescription;
+ public serverGroupOptions: string[];
+ public targets = StageConstants.TARGET_LIST;
+ public clusterList: string[];
+ public onAllocationChange: Function;
+ private application: Application;
+ private region: string;
+ private account: string;
+
+ public $onInit() {
+ const clusterFilter = AppListExtractor.clusterFilterForCredentialsAndRegion(this.account, this.region);
+ this.clusterList = AppListExtractor.getClusters([this.application], clusterFilter);
+ }
+
+ public getServerGroupOptions(): string[] {
+ if (this.allocationDescription.revisionName) {
+ return uniq(this.serverGroupOptions.concat(this.allocationDescription.revisionName));
+ } else {
+ return this.serverGroupOptions;
+ }
+ }
+
+ public onLocatorTypeChange(): void {
+ // Prevents pipeline expressions (or non-existent server groups) from entering the dropdown.
+ if (!this.serverGroupOptions.includes(this.allocationDescription.revisionName)) {
+ delete this.allocationDescription.revisionName;
+ }
+ this.onAllocationChange();
+ }
+}
+
+const cloudrunStageAllocationConfigurationRow: IComponentOptions = {
+ bindings: {
+ application: '<',
+ region: '@',
+ account: '@',
+ allocationDescription: '<',
+ removeAllocation: '&',
+ serverGroupOptions: '<',
+ onAllocationChange: '&',
+ },
+ controller: CloudrunStageAllocationConfigurationRowCtrl,
+ templateUrl: require('./stageAllocationConfigurationRow.component.html'),
+};
+
+export const CLOUDRUN_STAGE_ALLOCATION_CONFIGURATION_ROW =
+ 'spinnaker.cloudrun.stageAllocationConfigurationRow.component';
+module(CLOUDRUN_STAGE_ALLOCATION_CONFIGURATION_ROW, [])
+ .component('cloudrunStageAllocationConfigurationRow', cloudrunStageAllocationConfigurationRow)
+ .component('cloudrunStageAllocationLabel', cloudrunStageAllocationLabel);
diff --git a/packages/cloudrun/src/loadBalancer/configure/wizard/wizard.controller.ts b/packages/cloudrun/src/loadBalancer/configure/wizard/wizard.controller.ts
new file mode 100644
index 00000000000..38ed17f38b6
--- /dev/null
+++ b/packages/cloudrun/src/loadBalancer/configure/wizard/wizard.controller.ts
@@ -0,0 +1,157 @@
+import type { StateService } from '@uirouter/angularjs';
+import type { IController } from 'angular';
+import { module } from 'angular';
+import type { IModalServiceInstance } from 'angular-ui-bootstrap';
+import { cloneDeep } from 'lodash';
+
+import type { Application } from '@spinnaker/core';
+import { LoadBalancerWriter, TaskMonitor } from '@spinnaker/core';
+
+import type { CloudrunLoadBalancerTransformer, ICloudrunTrafficSplitDescription } from '../../loadBalancerTransformer';
+import { CloudrunLoadBalancerUpsertDescription } from '../../loadBalancerTransformer';
+
+import './wizard.less';
+
+class CloudrunLoadBalancerWizardController implements IController {
+ public state = { loading: true };
+ public loadBalancer: CloudrunLoadBalancerUpsertDescription;
+ public heading: string;
+ public submitButtonLabel: string;
+ public taskMonitor: TaskMonitor;
+
+ public static $inject = [
+ '$scope',
+ '$state',
+ '$uibModalInstance',
+ 'application',
+ 'loadBalancer',
+ 'isNew',
+ 'forPipelineConfig',
+ 'cloudrunLoadBalancerTransformer',
+ 'wizardSubFormValidation',
+ ];
+ constructor(
+ public $scope: ng.IScope,
+ private $state: StateService,
+ private $uibModalInstance: IModalServiceInstance,
+ private application: Application,
+ loadBalancer: CloudrunLoadBalancerUpsertDescription,
+ public isNew: boolean,
+ private forPipelineConfig: boolean,
+ private cloudrunLoadBalancerTransformer: CloudrunLoadBalancerTransformer,
+ private wizardSubFormValidation: any,
+ ) {
+ this.submitButtonLabel = this.forPipelineConfig ? 'Done' : 'Update';
+
+ if (this.isNew) {
+ this.heading = 'Create New Load Balancer';
+ } else {
+ this.heading = `Edit ${[
+ loadBalancer.name,
+ loadBalancer.region,
+ loadBalancer.account || loadBalancer.credentials,
+ ].join(':')}`;
+ this.cloudrunLoadBalancerTransformer
+ .convertLoadBalancerForEditing(loadBalancer, application)
+ .then((convertedLoadBalancer) => {
+ this.loadBalancer = this.cloudrunLoadBalancerTransformer.convertLoadBalancerToUpsertDescription(
+ convertedLoadBalancer,
+ );
+ if (loadBalancer.split && !this.loadBalancer.splitDescription) {
+ this.loadBalancer.splitDescription = CloudrunLoadBalancerUpsertDescription.convertTrafficSplitToTrafficSplitDescription(
+ loadBalancer.split,
+ );
+ } else {
+ this.loadBalancer.splitDescription = loadBalancer.splitDescription;
+ }
+ this.loadBalancer.mapAllocationsToPercentages();
+ this.setTaskMonitor();
+ this.initializeFormValidation();
+ this.state.loading = false;
+ });
+ }
+ }
+
+ public submit(): any {
+ const description = cloneDeep(this.loadBalancer);
+ description.mapAllocationsToPercentages();
+ delete description.serverGroups;
+
+ if (this.forPipelineConfig) {
+ return this.$uibModalInstance.close(description);
+ } else {
+ return this.taskMonitor.submit(() => {
+ return LoadBalancerWriter.upsertLoadBalancer(description, this.application, 'Update');
+ });
+ }
+ }
+
+ public cancel(): void {
+ this.$uibModalInstance.dismiss();
+ }
+
+ public showSubmitButton(): boolean {
+ return this.wizardSubFormValidation.subFormsAreValid();
+ }
+
+ private setTaskMonitor(): void {
+ this.taskMonitor = new TaskMonitor({
+ application: this.application,
+ title: 'Updating your load balancer',
+ modalInstance: this.$uibModalInstance,
+ onTaskComplete: () => this.onTaskComplete(),
+ });
+ }
+
+ private initializeFormValidation(): void {
+ this.wizardSubFormValidation.config({ form: 'form', scope: this.$scope }).register({
+ page: 'basic-settings',
+ subForm: 'basicSettingsForm',
+ validators: [
+ {
+ watchString: 'ctrl.loadBalancer.splitDescription',
+ validator: (splitDescription: ICloudrunTrafficSplitDescription): boolean => {
+ return (
+ splitDescription.allocationDescriptions.reduce((sum, description) => sum + description.percent, 0) === 100
+ );
+ },
+ watchDeep: true,
+ },
+ ],
+ });
+ }
+
+ private onTaskComplete(): void {
+ this.application.getDataSource('loadBalancers').refresh();
+ this.application.getDataSource('loadBalancers').onNextRefresh(this.$scope, () => this.onApplicationRefresh());
+ }
+
+ private onApplicationRefresh(): void {
+ // If the user has already closed the modal, do not navigate to the new details view
+ if ((this.$scope as any).$$destroyed) {
+ // $$destroyed is not in the ng.IScope interface
+ return;
+ }
+
+ this.$uibModalInstance.dismiss();
+ const newStateParams = {
+ name: this.loadBalancer.name,
+ accountId: this.loadBalancer.credentials,
+ region: this.loadBalancer.region,
+ provider: 'cloudrun',
+ };
+
+ if (!this.$state.includes('**.loadBalancerDetails')) {
+ this.$state.go('.loadBalancerDetails', newStateParams);
+ } else {
+ this.$state.go('^.loadBalancerDetails', newStateParams);
+ }
+ }
+}
+
+export const CLOUDRUN_LOAD_BALANCER_WIZARD_CTRL = 'spinnaker.cloudrun.loadBalancer.wizard.controller';
+
+module(CLOUDRUN_LOAD_BALANCER_WIZARD_CTRL, []).controller(
+ 'cloudrunLoadBalancerWizardCtrl',
+ CloudrunLoadBalancerWizardController,
+);
diff --git a/packages/cloudrun/src/loadBalancer/configure/wizard/wizard.html b/packages/cloudrun/src/loadBalancer/configure/wizard/wizard.html
new file mode 100644
index 00000000000..970d993560d
--- /dev/null
+++ b/packages/cloudrun/src/loadBalancer/configure/wizard/wizard.html
@@ -0,0 +1,39 @@
+
diff --git a/packages/cloudrun/src/loadBalancer/configure/wizard/wizard.less b/packages/cloudrun/src/loadBalancer/configure/wizard/wizard.less
new file mode 100644
index 00000000000..66523c33345
--- /dev/null
+++ b/packages/cloudrun/src/loadBalancer/configure/wizard/wizard.less
@@ -0,0 +1,9 @@
+cloudrun-load-balancer-basic-settings {
+ a.btn.btn-link {
+ padding: 0;
+ }
+
+ .form-group {
+ margin-top: 0.4rem;
+ }
+}
diff --git a/packages/cloudrun/src/loadBalancer/details/details.controller.ts b/packages/cloudrun/src/loadBalancer/details/details.controller.ts
new file mode 100644
index 00000000000..d0555e70b45
--- /dev/null
+++ b/packages/cloudrun/src/loadBalancer/details/details.controller.ts
@@ -0,0 +1,140 @@
+import type { StateService } from '@uirouter/angularjs';
+import type { IController, IScope } from 'angular';
+import { module } from 'angular';
+import type { IModalService } from 'angular-ui-bootstrap';
+import { cloneDeep } from 'lodash';
+
+import type { Application, ILoadBalancer, ILoadBalancerDeleteCommand } from '@spinnaker/core';
+import { ConfirmationModalService, LoadBalancerWriter } from '@spinnaker/core';
+import type { ICloudrunLoadBalancer } from '../../common/domain/index';
+
+interface ILoadBalancerFromStateParams {
+ accountId: string;
+ region: string;
+ name: string;
+}
+
+class CloudrunLoadBalancerDetailsController implements IController {
+ public state = { loading: true };
+ private loadBalancerFromParams: ILoadBalancerFromStateParams;
+ public loadBalancer: ICloudrunLoadBalancer;
+
+ public static $inject = ['$uibModal', '$state', '$scope', 'loadBalancer', 'app'];
+ constructor(
+ private $uibModal: IModalService,
+ private $state: StateService,
+ private $scope: IScope,
+ loadBalancer: ILoadBalancerFromStateParams,
+ private app: Application,
+ ) {
+ this.loadBalancerFromParams = loadBalancer;
+ this.app
+ .getDataSource('loadBalancers')
+ .ready()
+ .then(() => this.extractLoadBalancer());
+ }
+
+ // edit loadbalancer to change traffic
+
+ public editLoadBalancer(): void {
+ this.$uibModal.open({
+ templateUrl: require('../configure/wizard/wizard.html'),
+ controller: 'cloudrunLoadBalancerWizardCtrl as ctrl',
+ size: 'lg',
+ resolve: {
+ application: () => this.app,
+ loadBalancer: () => cloneDeep(this.loadBalancer),
+ isNew: () => false,
+ forPipelineConfig: () => false,
+ },
+ });
+ }
+
+ private extractLoadBalancer(): void {
+ this.loadBalancer = this.app.getDataSource('loadBalancers').data.find((test: ILoadBalancer) => {
+ return test.name === this.loadBalancerFromParams.name && test.account === this.loadBalancerFromParams.accountId;
+ }) as ICloudrunLoadBalancer;
+
+ if (this.loadBalancer) {
+ this.state.loading = false;
+ this.app.getDataSource('loadBalancers').onRefresh(this.$scope, () => this.extractLoadBalancer());
+ } else {
+ this.autoClose();
+ }
+ }
+
+ public deleteLoadBalancer(): void {
+ const taskMonitor = {
+ application: this.app,
+ title: 'Deleting ' + this.loadBalancer.name,
+ };
+
+ const submitMethod = () => {
+ const loadBalancer: ILoadBalancerDeleteCommand = {
+ cloudProvider: this.loadBalancer.cloudProvider,
+ loadBalancerName: this.loadBalancer.name,
+ credentials: this.loadBalancer.account,
+ };
+ return LoadBalancerWriter.deleteLoadBalancer(loadBalancer, this.app);
+ };
+
+ ConfirmationModalService.confirm({
+ header: 'Really delete ' + this.loadBalancer.name + '?',
+ buttonText: 'Delete ' + this.loadBalancer.name,
+ body: this.getConfirmationModalBodyHtml(),
+ account: this.loadBalancer.account,
+ taskMonitorConfig: taskMonitor,
+ submitMethod,
+ });
+ }
+
+ public canDeleteLoadBalancer(): boolean {
+ return this.loadBalancer.name !== 'default';
+ }
+
+ private getConfirmationModalBodyHtml(): string {
+ const serverGroupNames = this.loadBalancer.serverGroups.map((serverGroup) => serverGroup.name);
+ const hasAny = serverGroupNames ? serverGroupNames.length > 0 : false;
+ const hasMoreThanOne = serverGroupNames ? serverGroupNames.length > 1 : false;
+
+ // HTML accepted by the confirmationModalService is static (i.e., not managed by angular).
+ if (hasAny) {
+ if (hasMoreThanOne) {
+ const listOfServerGroupNames = serverGroupNames.map((name) => `${name} `).join('');
+ return `
+
+ Deleting ${this.loadBalancer.name} will destroy the following server groups:
+
+ ${listOfServerGroupNames}
+
+
+
+ `;
+ } else {
+ return `
+
+ Deleting ${this.loadBalancer.name} will destroy ${serverGroupNames[0]} .
+
+
+ `;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ private autoClose(): void {
+ if (this.$scope.$$destroyed) {
+ return;
+ } else {
+ this.$state.params.allowModalToStayOpen = true;
+ this.$state.go('^', null, { location: 'replace' });
+ }
+ }
+}
+
+export const CLOUDRUN_LOAD_BALANCER_DETAILS_CTRL = 'spinnaker.cloudrun.loadBalancerDetails.controller';
+module(CLOUDRUN_LOAD_BALANCER_DETAILS_CTRL, []).controller(
+ 'cloudrunLoadBalancerDetailsCtrl',
+ CloudrunLoadBalancerDetailsController,
+);
diff --git a/packages/cloudrun/src/loadBalancer/details/details.html b/packages/cloudrun/src/loadBalancer/details/details.html
new file mode 100644
index 00000000000..0b6eb57b28c
--- /dev/null
+++ b/packages/cloudrun/src/loadBalancer/details/details.html
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+ In
+
+ Region
+ {{ctrl.loadBalancer.region}}
+ Server Groups
+
+
+
+
+
+
+
+
+
+ {{trafficTarget.revisionName}}:{{trafficTarget.percent}}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/cloudrun/src/loadBalancer/index.ts b/packages/cloudrun/src/loadBalancer/index.ts
new file mode 100644
index 00000000000..d2cf8b08313
--- /dev/null
+++ b/packages/cloudrun/src/loadBalancer/index.ts
@@ -0,0 +1,3 @@
+export * from './loadBalancerTransformer';
+export * from './details/details.controller';
+export * from './configure/wizard/wizard.controller';
diff --git a/packages/cloudrun/src/loadBalancer/loadBalancerTransformer.ts b/packages/cloudrun/src/loadBalancer/loadBalancerTransformer.ts
new file mode 100644
index 00000000000..ed1be0485cc
--- /dev/null
+++ b/packages/cloudrun/src/loadBalancer/loadBalancerTransformer.ts
@@ -0,0 +1,168 @@
+import { module } from 'angular';
+import { camelCase, chain, cloneDeep, filter, get, has, reduce } from 'lodash';
+
+import type {
+ Application,
+ IInstance,
+ IInstanceCounts,
+ ILoadBalancer,
+ ILoadBalancerUpsertCommand,
+ IServerGroup,
+} from '@spinnaker/core';
+//import type { ICloudrunLoadBalancer, ICloudrunTrafficSplit, ShardBy } from '../common/domain/index';
+import type { ICloudrunLoadBalancer, ICloudrunTrafficSplit } from '../common/domain/index';
+
+export interface ICloudrunAllocationDescription {
+ revisionName?: string;
+ target?: string;
+ cluster?: string;
+ percent: number;
+}
+
+export interface ICloudrunTrafficSplitDescription {
+ allocationDescriptions: ICloudrunAllocationDescription[];
+}
+
+export class CloudrunLoadBalancerUpsertDescription implements ILoadBalancerUpsertCommand, ICloudrunLoadBalancer {
+ public credentials: string;
+ public account: string;
+ public loadBalancerName: string;
+ public name: string;
+ public splitDescription: ICloudrunTrafficSplitDescription;
+ public split?: ICloudrunTrafficSplit;
+ public migrateTraffic: boolean;
+ public region: string;
+ public cloudProvider: string;
+ public serverGroups?: any[];
+
+ public static convertTrafficSplitToTrafficSplitDescription(
+ split: ICloudrunTrafficSplit,
+ ): ICloudrunTrafficSplitDescription {
+ const allocationDescriptions = reduce(
+ split.trafficTargets,
+ (acc: any, trafficTarget: any) => {
+ const { revisionName, percent } = trafficTarget;
+ return acc.concat({ percent, revisionName, locatorType: 'fromExisting' });
+ },
+ [],
+ );
+ return { allocationDescriptions };
+ }
+
+ constructor(loadBalancer: ICloudrunLoadBalancer) {
+ this.credentials = loadBalancer.account || loadBalancer.credentials;
+ this.account = this.credentials;
+ this.cloudProvider = loadBalancer.cloudProvider;
+ this.loadBalancerName = loadBalancer.name;
+ this.name = loadBalancer.name;
+ this.region = loadBalancer.region;
+ this.migrateTraffic = loadBalancer.migrateTraffic || false;
+ this.serverGroups = loadBalancer.serverGroups;
+ }
+
+ public mapAllocationsToDecimals() {
+ this.splitDescription.allocationDescriptions.forEach((description) => {
+ description.percent = description.percent / 100;
+ });
+ }
+
+ public mapAllocationsToPercentages() {
+ this.splitDescription.allocationDescriptions.forEach((description) => {
+ // An allocation percent has at most one decimal place.
+ description.percent = Math.round(description.percent);
+ });
+ }
+}
+
+export class CloudrunLoadBalancerTransformer {
+ public static $inject = ['$q'];
+ constructor(private $q: ng.IQService) {}
+ public normalizeLoadBalancer(loadBalancer: ILoadBalancer): PromiseLike {
+ loadBalancer.provider = loadBalancer.type;
+ loadBalancer.instanceCounts = this.buildInstanceCounts(loadBalancer.serverGroups);
+ loadBalancer.instances = [];
+ loadBalancer.serverGroups.forEach((serverGroup) => {
+ serverGroup.account = loadBalancer.account;
+ serverGroup.region = loadBalancer.region;
+
+ if (serverGroup.detachedInstances) {
+ serverGroup.detachedInstances = (serverGroup.detachedInstances as any).map((id: string) => ({ id }));
+ }
+ serverGroup.instances = serverGroup.instances
+ .concat(serverGroup.detachedInstances || [])
+ .map((instance: any) => this.transformInstance(instance, loadBalancer));
+ });
+
+ const activeServerGroups = filter(loadBalancer.serverGroups, { isDisabled: false });
+ loadBalancer.instances = chain(activeServerGroups).map('instances').flatten().value() as IInstance[];
+ return this.$q.resolve(loadBalancer);
+ }
+
+ public convertLoadBalancerForEditing(
+ loadBalancer: ICloudrunLoadBalancer,
+ application: Application,
+ ): PromiseLike {
+ return application
+ .getDataSource('loadBalancers')
+ .ready()
+ .then(() => {
+ const upToDateLoadBalancer = application
+ .getDataSource('loadBalancers')
+ .data.find((candidate: ILoadBalancer) => {
+ return (
+ candidate.name === loadBalancer.name &&
+ (candidate.account === loadBalancer.account || candidate.account === loadBalancer.credentials)
+ );
+ });
+
+ if (upToDateLoadBalancer) {
+ loadBalancer.serverGroups = cloneDeep(upToDateLoadBalancer.serverGroups);
+ }
+ return loadBalancer;
+ });
+ }
+
+ public convertLoadBalancerToUpsertDescription(
+ loadBalancer: ICloudrunLoadBalancer,
+ ): CloudrunLoadBalancerUpsertDescription {
+ return new CloudrunLoadBalancerUpsertDescription(loadBalancer);
+ }
+
+ private buildInstanceCounts(serverGroups: IServerGroup[]): IInstanceCounts {
+ const instanceCounts: IInstanceCounts = chain(serverGroups)
+ .map('instances')
+ .flatten()
+ .reduce(
+ (acc: IInstanceCounts, instance: any) => {
+ if (has(instance, 'health.state')) {
+ acc[camelCase(instance.health.state)]++;
+ }
+ return acc;
+ },
+ { up: 0, down: 0, outOfService: 0, succeeded: 0, failed: 0, starting: 0, unknown: 0 },
+ )
+ .value();
+
+ instanceCounts.outOfService += chain(serverGroups).map('detachedInstances').flatten().value().length;
+ return instanceCounts;
+ }
+
+ private transformInstance(instance: any, loadBalancer: ILoadBalancer) {
+ instance.provider = loadBalancer.type;
+ instance.account = loadBalancer.account;
+ instance.region = loadBalancer.region;
+ instance.loadBalancers = [loadBalancer.name];
+ const health = instance.health || {};
+ instance.healthState = get(instance, 'health.state') || 'OutOfService';
+ instance.health = [health];
+
+ return instance as IInstance;
+ }
+}
+
+export const CLOUDRUN_LOAD_BALANCER_TRANSFORMER = 'spinnaker.cloudrun.loadBalancer.transformer.service';
+
+module(CLOUDRUN_LOAD_BALANCER_TRANSFORMER, []).service(
+ 'cloudrunLoadBalancerTransformer',
+ CloudrunLoadBalancerTransformer,
+);
diff --git a/packages/cloudrun/src/logo/cloudrun.icon.svg b/packages/cloudrun/src/logo/cloudrun.icon.svg
new file mode 100644
index 00000000000..91d2c6dc124
--- /dev/null
+++ b/packages/cloudrun/src/logo/cloudrun.icon.svg
@@ -0,0 +1,27 @@
+
+
+
+
+ Icon_24px_CloudRun_Color
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/cloudrun/src/logo/cloudrun.logo.less b/packages/cloudrun/src/logo/cloudrun.logo.less
new file mode 100644
index 00000000000..914b571a583
--- /dev/null
+++ b/packages/cloudrun/src/logo/cloudrun.logo.less
@@ -0,0 +1,6 @@
+.cloud-provider-logo {
+ .icon-cloudrun {
+ mask-image: url(cloudrun.icon.svg);
+ background-color: #4285f4;
+ }
+}
diff --git a/packages/cloudrun/src/logo/cloudrun.logo.png b/packages/cloudrun/src/logo/cloudrun.logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..423cf5cb794b46adbe969afbaa51760b92de27d7
GIT binary patch
literal 1338
zcmV-A1;zS_P)5000F6Nkls5{`8~QYj<*Jrs3f-mNYa9tlR$9&CJS{X&7|nM
zA;-$yC7WrgcWZpts6d!A&T?@#7z*^%UvgQ{(1B&R>tA;AA;D~G>ipF6#G`&aOw9T_I>7$#U)8*B-LANZ&%ZP
zbYZ6;D%q10=@B^-P_xm7J8!4Rs~W}6!Ezs7*}ck
zaz^PgXaQV)SC#Zid9ybRWHdYU*(<;~4PXN*H
zI0fGkfN7RzJ;3+Y+A#kvilX{q{M0RD&Epo#OVr+3<|I(mwfb@RVpx6Cl0HGw1+j+!
ziAJ3nP3h;;IQ2CszlV}c*zv42qD0G82{m3ny8FiJ)WiuSy-cho1TRhnraI4td+!)?
zX5AAOHK?_kQY01Na~%@=L117=2ZoRa;wlxFGKv#MVP?%BsNWnOGY8h&)L5F>PJSn;
zApPS20zriR4^>|o%TxuYAl3p{YXnG1-&Zm$p=^5aZi=1H#f44n?3M9xTL2e+9SP`(
zO_G7W!7ztD^c3;yR2YXKK1>0n`i84&23#4h!!wsddKty@cGyIAlG-g+?B^6hOoI
z0RD~=uR$4VMgka?fmCl1e|f9|mk(Be$=jm2)8e*65qhRJQ1
z(y8r+u0um)m&YCdrqVyQHV0TW8ImAy$)t8DUI6N*<@(R3l8zLu#r
zO40deFI(&_;k(0N)YvH#UkAZEdZJ3@{rcx1zP&c`^hy?e`J;r6>#-dpf->9Mw1s~=
z6vPu3b_vQUkELhYfnt+D%|lbsQ99tDl$?p=cN_=UV+8X1S?1xw$#*CH
zdz0Wh0I-**8;Ailjrn%X?MD~JQQkkFGODC{LL?xYg&^KyC}sh$e%Zv*KG`(ms@9Ci
zckR-F5>T=u+EPY=7*NreZ+GkIMU6tI3PcN{eT9g(5ZH7aB7}%CW0jFWCKWwBVx!+@
wX7ZfoIl{!OtqpIGkz-+m7S)+(0@K<54-F&Z this.$scope.application,
+ },
+ })
+ .result.then((newLoadBalancer: ILoadBalancer) => {
+ this.$scope.stage.loadBalancers.push(newLoadBalancer);
+ })
+ .catch(() => {});
+ }
+
+ public editLoadBalancer(index: number) {
+ const config = CloudProviderRegistry.getValue('cloudrun', 'loadBalancer');
+ this.$uibModal
+ .open({
+ templateUrl: config.createLoadBalancerTemplateUrl,
+ controller: `${config.createLoadBalancerController} as ctrl`,
+ size: 'lg',
+ resolve: {
+ application: () => this.$scope.application,
+ loadBalancer: () => cloneDeep(this.$scope.stage.loadBalancers[index]),
+ isNew: () => false,
+ forPipelineConfig: () => true,
+ },
+ })
+ .result.then((updatedLoadBalancer: ILoadBalancer) => {
+ this.$scope.stage.loadBalancers[index] = updatedLoadBalancer;
+ })
+ .catch(() => {});
+ }
+
+ public removeLoadBalancer(index: number): void {
+ this.$scope.stage.loadBalancers.splice(index, 1);
+ }
+}
+
+export const CLOUDRUN_EDIT_LOAD_BALANCER_STAGE = 'spinnaker.cloudrun.pipeline.stage.editLoadBalancerStage';
+module(CLOUDRUN_EDIT_LOAD_BALANCER_STAGE, [CLOUDRUN_LOAD_BALANCER_CHOICE_MODAL_CTRL])
+ .config(() => {
+ Registry.pipeline.registerStage({
+ label: 'Edit Load Balancer (Cloudrun)',
+ description: 'Edits a load balancer',
+ key: 'upsertCloudrunLoadBalancers',
+ cloudProvider: 'cloudrun',
+ templateUrl: require('./editLoadBalancerStage.html'),
+ executionDetailsUrl: require('./editLoadBalancerExecutionDetails.html'),
+ executionConfigSections: ['editLoadBalancerConfig', 'taskStatus'],
+ controller: 'cloudrunEditLoadBalancerStageCtrl',
+ controllerAs: 'editLoadBalancerStageCtrl',
+ validators: [],
+ });
+ })
+ .controller('cloudrunEditLoadBalancerStageCtrl', CloudrunEditLoadBalancerStageCtrl);
diff --git a/packages/cloudrun/src/pipeline/stages/editLoadBalancer/editLoadBalancerExecutionDetails.html b/packages/cloudrun/src/pipeline/stages/editLoadBalancer/editLoadBalancerExecutionDetails.html
new file mode 100644
index 00000000000..1ee8b1224b4
--- /dev/null
+++ b/packages/cloudrun/src/pipeline/stages/editLoadBalancer/editLoadBalancerExecutionDetails.html
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+ Account
+ Name
+ Region
+
+
+
+
+
+
+
+ {{ loadBalancer.name }}
+ {{ loadBalancer.region }}
+
+
+
+
+
+
+
+
+
diff --git a/packages/cloudrun/src/pipeline/stages/editLoadBalancer/editLoadBalancerStage.html b/packages/cloudrun/src/pipeline/stages/editLoadBalancer/editLoadBalancerStage.html
new file mode 100644
index 00000000000..0772bd9d571
--- /dev/null
+++ b/packages/cloudrun/src/pipeline/stages/editLoadBalancer/editLoadBalancerStage.html
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+ Account
+ Name
+ Region
+ Actions
+
+
+
+
+
+
+
+ {{ loadBalancer.name }}
+ {{ loadBalancer.region }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Add load balancer
+
+
+
+
+
+
+
+
diff --git a/packages/cloudrun/src/pipeline/stages/editLoadBalancer/loadBalancerChoice.modal.controller.ts b/packages/cloudrun/src/pipeline/stages/editLoadBalancer/loadBalancerChoice.modal.controller.ts
new file mode 100644
index 00000000000..7daa4cd2194
--- /dev/null
+++ b/packages/cloudrun/src/pipeline/stages/editLoadBalancer/loadBalancerChoice.modal.controller.ts
@@ -0,0 +1,65 @@
+import type { IController } from 'angular';
+import { module } from 'angular';
+import type { IModalService, IModalServiceInstance } from 'angular-ui-bootstrap';
+import { cloneDeep } from 'lodash';
+
+import type { Application, ILoadBalancer } from '@spinnaker/core';
+import { CloudProviderRegistry } from '@spinnaker/core';
+
+class CloudrunLoadBalancerChoiceModalCtrl implements IController {
+ public state = { loading: true };
+ public loadBalancers: ILoadBalancer[];
+ public selectedLoadBalancer: ILoadBalancer;
+
+ public static $inject = ['$uibModal', '$uibModalInstance', 'application'];
+ constructor(
+ private $uibModal: IModalService,
+ private $uibModalInstance: IModalServiceInstance,
+ private application: Application,
+ ) {
+ this.initialize();
+ }
+
+ public submit(): void {
+ const config = CloudProviderRegistry.getValue('cloudrun', 'loadBalancer');
+ const updatedLoadBalancerPromise = this.$uibModal.open({
+ templateUrl: config.createLoadBalancerTemplateUrl,
+ controller: `${config.createLoadBalancerController} as ctrl`,
+ size: 'lg',
+ resolve: {
+ application: () => this.application,
+ loadBalancer: () => cloneDeep(this.selectedLoadBalancer),
+ isNew: () => false,
+ forPipelineConfig: () => true,
+ },
+ }).result;
+
+ this.$uibModalInstance.close(updatedLoadBalancerPromise);
+ }
+
+ public cancel(): void {
+ this.$uibModalInstance.dismiss();
+ }
+
+ private initialize(): void {
+ this.application
+ .getDataSource('loadBalancers')
+ .ready()
+ .then(() => {
+ this.loadBalancers = (this.application.loadBalancers.data as ILoadBalancer[]).filter(
+ (candidate) => candidate.cloudProvider === 'cloudrun',
+ );
+
+ if (this.loadBalancers.length) {
+ this.selectedLoadBalancer = this.loadBalancers[0];
+ }
+ this.state.loading = false;
+ });
+ }
+}
+
+export const CLOUDRUN_LOAD_BALANCER_CHOICE_MODAL_CTRL = 'spinnaker.Cloudrun.loadBalancerChoiceModal.controller';
+module(CLOUDRUN_LOAD_BALANCER_CHOICE_MODAL_CTRL, []).controller(
+ 'cloudrunLoadBalancerChoiceModelCtrl',
+ CloudrunLoadBalancerChoiceModalCtrl,
+);
diff --git a/packages/cloudrun/src/pipeline/stages/editLoadBalancer/loadBalancerChoice.modal.html b/packages/cloudrun/src/pipeline/stages/editLoadBalancer/loadBalancerChoice.modal.html
new file mode 100644
index 00000000000..7d1faf64e35
--- /dev/null
+++ b/packages/cloudrun/src/pipeline/stages/editLoadBalancer/loadBalancerChoice.modal.html
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+ Spinnaker cannot create a load balancer for Cloud Run. A Spinnaker load balancer maps to a Cloud Run service
+ which along with a Revision are created from Create Server Group page using a
+
+
yaml file
+
+ If a service does not exist when a Revision is deployed, it will be created. It will then be editable as a load
+ balancer within Spinnaker.
+
+
+
+
+
+
diff --git a/packages/cloudrun/src/serverGroup/configure/serverGroupCommandBuilder.service.ts b/packages/cloudrun/src/serverGroup/configure/serverGroupCommandBuilder.service.ts
new file mode 100644
index 00000000000..dd3629d3a5d
--- /dev/null
+++ b/packages/cloudrun/src/serverGroup/configure/serverGroupCommandBuilder.service.ts
@@ -0,0 +1,254 @@
+import { module } from 'angular';
+import { cloneDeep } from 'lodash';
+import { $q } from 'ngimport';
+
+import type {
+ Application,
+ IAccountDetails,
+ IExpectedArtifact,
+ IMoniker,
+ IPipeline,
+ IServerGroupCommand,
+ IServerGroupCommandViewState,
+ IStage,
+} from '@spinnaker/core';
+import { AccountService } from '@spinnaker/core';
+
+import { CloudrunProviderSettings } from '../../cloudrun.settings';
+import type { CloudrunDeployDescription } from '../serverGroupTransformer.service';
+
+export enum ServerGroupSource {
+ TEXT = 'text',
+ ARTIFACT = 'artifact',
+}
+
+export interface ICloudrunServerGroupCommandData {
+ command: ICloudrunServerGroupCommand;
+ metadata: ICloudrunServerGroupCommandMetadata;
+ stack: string;
+ freeFormDetails: string;
+ configFiles: string[];
+}
+
+export interface ICloudrunServerGroupCommand extends Omit {
+ application?: string;
+ stack?: string;
+ detail?: string;
+ account: string;
+ configFiles: string[];
+ freeFormDetails: string;
+ region: string;
+ regions: [];
+ isNew?: boolean;
+ cloudProvider: string;
+ provider: string;
+ selectedProvider: string;
+ manifest: any; // deprecated
+ manifests: any[];
+ relationships: ICloudrunServerGroupSpinnakerRelationships;
+ moniker: IMoniker;
+ manifestArtifactId?: string;
+ manifestArtifactAccount?: string;
+ source: ServerGroupSource;
+ versioned?: boolean;
+ gitCredentialType?: string;
+ viewState: IServerGroupCommandViewState;
+ mode: string;
+ credentials: string;
+ sourceType: string;
+ configArtifacts: any[];
+ interestingHealthProviderNames: [];
+ fromArtifact: boolean;
+}
+
+export interface IViewState {
+ mode: string;
+ submitButtonLabel: string;
+ disableStrategySelection: boolean;
+ stage?: IStage;
+ pipeline?: IPipeline;
+}
+
+export interface ICloudrunServerGroupCommandMetadata {
+ backingData: any;
+}
+
+export interface ICloudrunServerGroupSpinnakerRelationships {
+ loadBalancers?: string[];
+ securityGroups?: string[];
+}
+
+const getSubmitButtonLabel = (mode: string): string => {
+ switch (mode) {
+ case 'createPipeline':
+ return 'Add';
+ case 'editPipeline':
+ return 'Done';
+ default:
+ return 'Create';
+ }
+};
+
+export class CloudrunV2ServerGroupCommandBuilder {
+ // new add servergroup
+ public buildNewServerGroupCommand(app: Application): PromiseLike {
+ return CloudrunServerGroupCommandBuilder.buildNewServerGroupCommand(app, 'cloudrun', 'create');
+ }
+
+ // add servergroup from deploy stage of pipeline
+ public buildNewServerGroupCommandForPipeline(_stage: IStage, pipeline: IPipeline) {
+ return CloudrunServerGroupCommandBuilder.buildNewServerGroupCommandForPipeline(_stage, pipeline);
+ }
+
+ // edit servergroup from deploy stage of pipeline
+ // add servergroup from deploy stage of pipeline
+ public buildServerGroupCommandFromPipeline(
+ app: Application,
+ cluster: CloudrunDeployDescription,
+ _stage: IStage,
+ pipeline: IPipeline,
+ ) {
+ return CloudrunServerGroupCommandBuilder.buildServerGroupCommandFromPipeline(app, cluster, _stage, pipeline);
+ }
+}
+
+export class CloudrunServerGroupCommandBuilder {
+ public static $inject = ['$q'];
+ public static ServerGroupCommandIsValid(command: ICloudrunServerGroupCommand): boolean {
+ if (!command.moniker) {
+ return false;
+ }
+
+ if (!command.moniker.app) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static copyAndCleanCommand(input: ICloudrunServerGroupCommand): ICloudrunServerGroupCommand {
+ const command = cloneDeep(input);
+ return command;
+ }
+
+ // deploy stage : construct servergroup command
+ public static buildNewServerGroupCommandForPipeline(stage: IStage, pipeline: IPipeline): any {
+ const command: any = this.buildNewServerGroupCommand(
+ { name: pipeline.application } as Application,
+ 'cloudrun',
+ 'createPipeline',
+ );
+ command.viewState = {
+ ...command.viewState,
+ pipeline,
+ requiresTemplateSelection: true,
+ stage,
+ };
+ return command;
+ }
+
+ private static getExpectedArtifacts(pipeline: IPipeline): IExpectedArtifact[] {
+ return pipeline.expectedArtifacts || [];
+ }
+
+ public static buildServerGroupCommandFromPipeline(
+ app: Application,
+ cluster: CloudrunDeployDescription,
+ _stage: IStage,
+ pipeline: IPipeline,
+ ): PromiseLike {
+ return CloudrunServerGroupCommandBuilder.buildNewServerGroupCommand(app, 'cloudrun', 'editPipeline').then(
+ (command: ICloudrunServerGroupCommandData) => {
+ command = {
+ ...command,
+ ...cluster,
+
+ backingData: {
+ ...command.metadata.backingData.backingData,
+
+ expectedArtifacts: CloudrunServerGroupCommandBuilder.getExpectedArtifacts(pipeline),
+ },
+ credentials: cluster.account || command.metadata.backingData.credentials,
+ viewState: {
+ ...command.metadata.backingData.viewState,
+ stage: _stage,
+ pipeline,
+ },
+ } as ICloudrunServerGroupCommandData;
+ return command;
+ },
+ );
+ }
+
+ public static getCredentials(accounts: IAccountDetails[]): string {
+ const accountNames: string[] = (accounts || []).map((account) => account.name);
+ const defaultCredentials: string = CloudrunProviderSettings.defaults.account;
+
+ return accountNames.includes(defaultCredentials) ? defaultCredentials : accountNames[0];
+ }
+
+ public static getRegion(accounts: any[], credentials: string): string {
+ const account = accounts.find((_account) => _account.name === credentials);
+ return account ? account.region : null;
+ }
+
+ // new servergroup command
+ public static buildNewServerGroupCommand(
+ app: Application,
+ sourceAccount: string,
+ mode: string,
+ ): PromiseLike {
+ const dataToFetch = {
+ accounts: AccountService.getAllAccountDetailsForProvider('cloudrun'),
+ artifactAccounts: AccountService.getArtifactAccounts(),
+ };
+
+ return $q.all(dataToFetch).then((backingData: { accounts: IAccountDetails[] }) => {
+ const { accounts } = backingData;
+
+ const account = accounts.some((a) => a.name === sourceAccount)
+ ? accounts.find((a) => a.name === sourceAccount).name
+ : accounts.length
+ ? accounts[0].name
+ : null;
+ const viewState: IViewState = {
+ mode,
+ submitButtonLabel: getSubmitButtonLabel(mode),
+ disableStrategySelection: mode === 'create',
+ };
+ const credentials = account ? account : this.getCredentials(accounts);
+ const region = this.getRegion(backingData.accounts, credentials);
+ const cloudProvider = 'cloudrun';
+
+ return {
+ command: {
+ application: app.name,
+ configFiles: [''],
+ cloudProvider,
+ selectedProvider: cloudProvider,
+ provider: cloudProvider,
+ region,
+ credentials,
+ gitCredentialType: 'NONE',
+ manifest: null,
+ sourceType: 'git',
+ configArtifacts: [],
+ interestingHealthProviderNames: [],
+ fromArtifact: false,
+ account,
+ viewState,
+ },
+ metadata: {
+ backingData,
+ },
+ } as ICloudrunServerGroupCommandData;
+ });
+ }
+}
+
+export const CLOUDRUN_SERVER_GROUP_COMMAND_BUILDER = 'spinnaker.cloudrun.serverGroup.commandBuilder.service';
+
+module(CLOUDRUN_SERVER_GROUP_COMMAND_BUILDER, []).service(
+ 'cloudrunV2ServerGroupCommandBuilder',
+ CloudrunV2ServerGroupCommandBuilder,
+);
diff --git a/packages/cloudrun/src/serverGroup/configure/wizard/BasicSettings.tsx b/packages/cloudrun/src/serverGroup/configure/wizard/BasicSettings.tsx
new file mode 100644
index 00000000000..9a37499fd31
--- /dev/null
+++ b/packages/cloudrun/src/serverGroup/configure/wizard/BasicSettings.tsx
@@ -0,0 +1,160 @@
+import type { FormikProps } from 'formik';
+import React from 'react';
+
+import type { Application, IAccount, IServerGroup } from '@spinnaker/core';
+import { AccountSelectInput, HelpField, NameUtils, ReactInjector, ServerGroupNamePreview } from '@spinnaker/core';
+
+import type { ICloudrunServerGroupCommandData } from '../serverGroupCommandBuilder.service';
+
+export interface IServerGroupBasicSettingsProps {
+ accounts: IAccount[];
+ onAccountSelect: (account: string) => void;
+ selectedAccount: string;
+ formik: IWizardServerGroupBasicSettingsProps['formik'];
+ onEnterStack: (stack: string) => void;
+ detailsChanged: (detail: string) => void;
+ app: Application;
+}
+
+export interface IServerGroupBasicSettingsState {
+ namePreview: string;
+ createsNewCluster: boolean;
+ latestServerGroup: IServerGroup;
+}
+
+export function ServerGroupBasicSettings({
+ accounts,
+ onAccountSelect,
+ selectedAccount,
+ formik,
+ onEnterStack,
+ detailsChanged,
+ app,
+}: IServerGroupBasicSettingsProps) {
+ const { values } = formik;
+ const { stack = '', freeFormDetails } = values;
+
+ const namePreview = NameUtils.getClusterName(app.name, stack, freeFormDetails);
+ const createsNewCluster = !app.clusters.find((c) => c.name === namePreview);
+ const inCluster = (app.serverGroups.data as IServerGroup[])
+ .filter((serverGroup) => {
+ return (
+ serverGroup.cluster === namePreview &&
+ serverGroup.account === values.command.credentials &&
+ serverGroup.region === values.command.region
+ );
+ })
+ .sort((a, b) => a.createdTime - b.createdTime);
+ const latestServerGroup = inCluster.length ? inCluster.pop() : null;
+
+ const navigateToLatestServerGroup = () => {
+ const { values } = formik;
+ const params = {
+ provider: values.command.selectedProvider,
+ accountId: latestServerGroup.account,
+ region: latestServerGroup.region,
+ serverGroup: latestServerGroup.name,
+ };
+
+ const { $state } = ReactInjector;
+ if ($state.is('home.applications.application.insight.clusters')) {
+ $state.go('.serverGroup', params);
+ } else {
+ $state.go('^.serverGroup', params);
+ }
+ };
+
+ return (
+
+
+
Account
+
+
onAccountSelect(evt.target.value)}
+ readOnly={false}
+ accounts={accounts}
+ provider="cloudrun"
+ />
+
+
+
+
+
+ Stack
+
+
+ onEnterStack(e.target.value)}
+ />
+
+
+
+
+
+ Detail
+
+
+ detailsChanged(e.target.value)}
+ />
+
+
+ {!values.command.viewState.hideClusterNamePreview && (
+
+ )}
+
+ );
+}
+
+export interface IWizardServerGroupBasicSettingsProps {
+ formik: FormikProps;
+ app: Application;
+}
+
+export class WizardServerGroupBasicSettings extends React.Component {
+ private accountUpdated = (account: string): void => {
+ const { formik } = this.props;
+ formik.values.command.account = account;
+ formik.setFieldValue('account', account);
+ };
+
+ private stackChanged = (stack: string): void => {
+ const { setFieldValue, values } = this.props.formik;
+ values.command.stack = stack;
+ setFieldValue('stack', stack);
+ };
+
+ private freeFormDetailsChanged = (freeFormDetails: string) => {
+ const { setFieldValue, values } = this.props.formik;
+ values.command.freeFormDetails = freeFormDetails;
+ setFieldValue('freeFormDetails', freeFormDetails);
+ };
+
+ public render() {
+ const { formik, app } = this.props;
+ return (
+
+ );
+ }
+}
diff --git a/packages/cloudrun/src/serverGroup/configure/wizard/ConfigFiles.tsx b/packages/cloudrun/src/serverGroup/configure/wizard/ConfigFiles.tsx
new file mode 100644
index 00000000000..330f0dc407d
--- /dev/null
+++ b/packages/cloudrun/src/serverGroup/configure/wizard/ConfigFiles.tsx
@@ -0,0 +1,75 @@
+import type { FormikProps } from 'formik';
+import React, { useState } from 'react';
+
+import { HelpField, TextAreaInput } from '@spinnaker/core';
+import type { ICloudrunServerGroupCommandData } from '../serverGroupCommandBuilder.service';
+
+export interface IServerGroupConfigFilesSettingsProps {
+ configFiles: string[];
+ onEnterConfig: (file: string[]) => void;
+}
+type configFiles = IServerGroupConfigFilesSettingsProps['configFiles'];
+
+export function ServerGroupConfigFilesSettings({ configFiles, onEnterConfig }: IServerGroupConfigFilesSettingsProps) {
+ const [configValues, setConfigValues] = useState(configFiles);
+
+ function mapTabToSpaces(event: any, i: number) {
+ if (event.which === 9) {
+ event.preventDefault();
+ const cursorPosition = event.target.selectionStart;
+ const inputValue = event.target.value;
+ event.target.value = `${inputValue.substring(0, cursorPosition)} ${inputValue.substring(cursorPosition)}`;
+ event.target.selectionStart += 2;
+ }
+ const newConfigValues = [...configValues];
+ newConfigValues[i] = event.target.value;
+ setConfigValues(newConfigValues);
+ onEnterConfig(newConfigValues);
+ }
+
+ return (
+
+
+ {configValues.map((configFile, index) => (
+ <>
+
+ Service Yaml
+ {' '}
+
+
+ mapTabToSpaces(e, index)}
+ />
+
+ >
+ ))}
+
+
+ );
+}
+
+export interface IWizardServerGroupConfigFilesSettingsProps {
+ formik: FormikProps;
+}
+
+export class WizardServerGroupConfigFilesSettings extends React.Component {
+ private configUpdated = (configFiles: string[]): void => {
+ const { formik } = this.props;
+ formik.values.command.configFiles = configFiles;
+ formik.setFieldValue('configFiles', configFiles);
+ };
+
+ // yaml config files input from server group wizard
+ public render() {
+ const { formik } = this.props;
+ return (
+
+ );
+ }
+}
diff --git a/packages/cloudrun/src/serverGroup/configure/wizard/serverGroupWizard.tsx b/packages/cloudrun/src/serverGroup/configure/wizard/serverGroupWizard.tsx
new file mode 100644
index 00000000000..3877f49aac4
--- /dev/null
+++ b/packages/cloudrun/src/serverGroup/configure/wizard/serverGroupWizard.tsx
@@ -0,0 +1,150 @@
+import React from 'react';
+import type { Application, IModalComponentProps, IStage } from '@spinnaker/core';
+//import type { IModalInstanceService } from 'angular-ui-bootstrap';
+import { noop, ReactInjector, ReactModal, TaskMonitor, WizardModal, WizardPage } from '@spinnaker/core';
+import { WizardServerGroupBasicSettings } from './BasicSettings';
+import { WizardServerGroupConfigFilesSettings } from './ConfigFiles';
+import type { ICloudrunServerGroupCommandData } from '../serverGroupCommandBuilder.service';
+import { CloudrunServerGroupCommandBuilder } from '../serverGroupCommandBuilder.service';
+
+export interface ICloudrunServerGroupModalProps extends IModalComponentProps {
+ title: string;
+ application: Application;
+ command: ICloudrunServerGroupCommandData;
+ isNew?: boolean;
+}
+
+export interface ICloudrunServerGroupModalState {
+ command: ICloudrunServerGroupCommandData;
+ loaded: boolean;
+ taskMonitor: TaskMonitor;
+}
+
+export class ServerGroupWizard extends React.Component {
+ public static defaultProps: Partial = {
+ closeModal: noop,
+ dismissModal: noop,
+ };
+
+ private _isUnmounted = false;
+
+ /* private serverGroupWriter: ServerGroupWriter; */
+ public static show(props: ICloudrunServerGroupModalProps): Promise {
+ const modalProps = { dialogClassName: 'wizard-modal modal-lg' };
+ return ReactModal.show(ServerGroupWizard, props, modalProps);
+ }
+
+ constructor(props: ICloudrunServerGroupModalProps) {
+ super(props);
+ if (!props.command) {
+ CloudrunServerGroupCommandBuilder.buildNewServerGroupCommand(props.application, 'cloudrun', 'create').then(
+ (command) => {
+ Object.assign(this.state.command, command);
+ this.setState({ loaded: true });
+ },
+ );
+ }
+
+ this.state = {
+ loaded: !!props.command,
+ command: props.command || ({} as ICloudrunServerGroupCommandData),
+ taskMonitor: new TaskMonitor({
+ application: props.application,
+ title: `${
+ props.command.command.viewState.submitButtonLabel === 'Create' ? 'Creating' : 'Updating'
+ } your Server Group`,
+ modalInstance: TaskMonitor.modalInstanceEmulation(() => this.props.dismissModal()),
+ onTaskComplete: this.onTaskComplete,
+ }),
+ };
+ }
+
+ private onTaskComplete = () => {
+ this.props.application.serverGroups.refresh();
+ this.props.application.serverGroups.onNextRefresh(null, this.onApplicationRefresh);
+ };
+
+ protected onApplicationRefresh = (): void => {
+ if (this._isUnmounted) {
+ return;
+ }
+
+ const { command } = this.props;
+ const { taskMonitor } = this.state;
+ const cloneStage = taskMonitor.task.execution.stages.find((stage: IStage) => stage.type === 'cloneServerGroup');
+ if (cloneStage && cloneStage.context['deploy.server.groups']) {
+ const newServerGroupName = cloneStage.context['deploy.server.groups'][command.command.region];
+ if (newServerGroupName) {
+ const newStateParams = {
+ serverGroup: newServerGroupName,
+ accountId: command.command.credentials,
+ region: command.command.region,
+ provider: 'cloudrun',
+ };
+ let transitionTo = '^.^.^.clusters.serverGroup';
+ if (ReactInjector.$state.includes('**.clusters.serverGroup')) {
+ // clone via details, all view
+ transitionTo = '^.serverGroup';
+ }
+ if (ReactInjector.$state.includes('**.clusters.cluster.serverGroup')) {
+ // clone or create with details open
+ transitionTo = '^.^.serverGroup';
+ }
+ if (ReactInjector.$state.includes('**.clusters')) {
+ // create new, no details open
+ transitionTo = '.serverGroup';
+ }
+ ReactInjector.$state.go(transitionTo, newStateParams);
+ }
+ }
+ };
+
+ private submit = (c: ICloudrunServerGroupCommandData): void => {
+ const command: any = CloudrunServerGroupCommandBuilder.copyAndCleanCommand(c.command);
+ const forPipelineConfig = command.viewState.mode === 'editPipeline' || command.viewState.mode === 'createPipeline';
+ if (forPipelineConfig) {
+ this.props.closeModal && this.props.closeModal(command);
+ } else {
+ //command.viewState.mode = 'create';
+ const submitMethod = () => ReactInjector.serverGroupWriter.cloneServerGroup(command, this.props.application);
+ this.state.taskMonitor.submit(submitMethod);
+ return null;
+ }
+ };
+ public render() {
+ const { dismissModal, application } = this.props;
+ const { loaded, taskMonitor, command } = this.state;
+ const labelButton = this.state.command.command.viewState.submitButtonLabel;
+
+ return (
+
+ heading={`${labelButton === 'Add' || labelButton === 'Create' ? 'Create New' : 'Update'} Server Group`}
+ initialValues={command}
+ loading={!loaded}
+ taskMonitor={taskMonitor}
+ dismissModal={dismissModal}
+ closeModal={this.submit}
+ submitButtonLabel={labelButton}
+ render={({ formik, nextIdx, wizard }) => (
+ <>
+ (
+
+ )}
+ />
+
+ }
+ />
+ >
+ )}
+ />
+ );
+ }
+}
diff --git a/packages/cloudrun/src/serverGroup/details/details.controller.ts b/packages/cloudrun/src/serverGroup/details/details.controller.ts
new file mode 100644
index 00000000000..6f1fd8ff7f5
--- /dev/null
+++ b/packages/cloudrun/src/serverGroup/details/details.controller.ts
@@ -0,0 +1,134 @@
+import type { IController, IScope } from 'angular';
+import { module } from 'angular';
+import type { Application, ILoadBalancer, IServerGroup, ServerGroupWriter } from '@spinnaker/core';
+import { ConfirmationModalService, SERVER_GROUP_WRITER, ServerGroupReader } from '@spinnaker/core';
+import { CloudrunHealth } from '../../common/cloudrunHealth';
+import type { ICloudrunServerGroup } from '../../interfaces';
+
+interface IServerGroupFromStateParams {
+ accountId: string;
+ region: string;
+ name: string;
+}
+
+class CloudrunServerGroupDetailsController implements IController {
+ public state = { loading: true };
+ public serverGroup: ICloudrunServerGroup;
+
+ public static $inject = ['$state', '$scope', 'serverGroup', 'app', 'serverGroupWriter'];
+ constructor(
+ private $state: any,
+ private $scope: IScope,
+ serverGroup: IServerGroupFromStateParams,
+ public app: Application,
+ private serverGroupWriter: ServerGroupWriter,
+ ) {
+ this.extractServerGroup(serverGroup)
+ .then(() => {
+ if (!this.$scope.$$destroyed) {
+ this.app.getDataSource('serverGroups').onRefresh(this.$scope, () => this.extractServerGroup(serverGroup));
+ }
+ })
+ .catch(() => this.autoClose());
+ }
+
+ // destroy existing server group
+ public canDestroyServerGroup(): boolean {
+ if (this.serverGroup) {
+ const isCurrentRevision = this.serverGroup.tags.isLatest;
+ if (isCurrentRevision) {
+ return false;
+ } else if (this.serverGroup.disabled) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ public destroyServerGroup(): void {
+ const stateParams = {
+ name: this.serverGroup.name,
+ accountId: this.serverGroup.account,
+ region: this.serverGroup.region,
+ };
+
+ const taskMonitor = {
+ application: this.app,
+ title: 'Destroying ' + this.serverGroup.name,
+ onTaskComplete: () => {
+ if (this.$state.includes('**.serverGroup', stateParams)) {
+ this.$state.go('^');
+ }
+ },
+ };
+
+ const submitMethod = (params: any) => this.serverGroupWriter.destroyServerGroup(this.serverGroup, this.app, params);
+
+ const confirmationModalParams = {
+ header: 'Really destroy ' + this.serverGroup.name + '?',
+ buttonText: 'Destroy ' + this.serverGroup.name,
+ account: this.serverGroup.account,
+ taskMonitorConfig: taskMonitor,
+ submitMethod,
+ askForReason: true,
+ platformHealthOnlyShowOverride: this.app.attributes.platformHealthOnlyShowOverride,
+ platformHealthType: CloudrunHealth.PLATFORM,
+
+ interestingHealthProviderNames: [] as string[],
+ };
+
+ if (this.app.attributes.platformHealthOnlyShowOverride && this.app.attributes.platformHealthOnly) {
+ confirmationModalParams.interestingHealthProviderNames = [CloudrunHealth.PLATFORM];
+ }
+
+ ConfirmationModalService.confirm(confirmationModalParams);
+ }
+
+ private autoClose(): void {
+ if (this.$scope.$$destroyed) {
+ return;
+ } else {
+ this.$state.params.allowModalToStayOpen = true;
+ this.$state.go('^', null, { location: 'replace' });
+ }
+ }
+
+ private extractServerGroup({ name, accountId, region }: IServerGroupFromStateParams): PromiseLike {
+ return ServerGroupReader.getServerGroup(this.app.name, accountId, region, name).then(
+ (serverGroupDetails: IServerGroup) => {
+ let fromApp = this.app.getDataSource('serverGroups').data.find((toCheck: IServerGroup) => {
+ return toCheck.name === name && toCheck.account === accountId && toCheck.region === region;
+ });
+
+ if (!fromApp) {
+ this.app.getDataSource('loadBalancers').data.some((loadBalancer: ILoadBalancer) => {
+ if (loadBalancer.account === accountId) {
+ return loadBalancer.serverGroups.some((toCheck: IServerGroup) => {
+ let result = false;
+ if (toCheck.name === name) {
+ fromApp = toCheck;
+ result = true;
+ }
+ return result;
+ });
+ } else {
+ return false;
+ }
+ });
+ }
+
+ this.serverGroup = { ...serverGroupDetails, ...fromApp };
+ this.state.loading = false;
+ },
+ );
+ }
+}
+export const CLOUDRUN_SERVER_GROUP_DETAILS_CTRL = 'spinnaker.cloudrun.serverGroup.details.controller';
+
+module(CLOUDRUN_SERVER_GROUP_DETAILS_CTRL, [SERVER_GROUP_WRITER]).controller(
+ 'cloudrunV2ServerGroupDetailsCtrl',
+ CloudrunServerGroupDetailsController,
+);
diff --git a/packages/cloudrun/src/serverGroup/details/details.html b/packages/cloudrun/src/serverGroup/details/details.html
new file mode 100644
index 00000000000..6bcd91474c2
--- /dev/null
+++ b/packages/cloudrun/src/serverGroup/details/details.html
@@ -0,0 +1,94 @@
+
+
+
+
+
+
Disabled
+
+
+
+ Created
+ {{ctrl.serverGroup.createdTime | timestamp}}
+ In
+
+ Region
+ {{ctrl.serverGroup.region}}
+
+
+
+
+
+ Min/Max
+ {{ctrl.serverGroup.capacity.min}}
+ Current
+ {{ctrl.serverGroup.instances.length}}
+
+
+ Min
+ {{ctrl.serverGroup.capacity.min}}
+ Max
+ {{ctrl.serverGroup.capacity.max}}
+ Current
+ {{ctrl.serverGroup.instances.length}}
+
+
+
+
+ Instances
+
+
+
+
+
+
+
diff --git a/packages/cloudrun/src/serverGroup/index.ts b/packages/cloudrun/src/serverGroup/index.ts
new file mode 100644
index 00000000000..4bfea14496c
--- /dev/null
+++ b/packages/cloudrun/src/serverGroup/index.ts
@@ -0,0 +1,3 @@
+export * from './configure/serverGroupCommandBuilder.service';
+export * from './serverGroupTransformer.service';
+export * from './details/details.controller';
diff --git a/packages/cloudrun/src/serverGroup/serverGroupTransformer.service.ts b/packages/cloudrun/src/serverGroup/serverGroupTransformer.service.ts
new file mode 100644
index 00000000000..577b54b0638
--- /dev/null
+++ b/packages/cloudrun/src/serverGroup/serverGroupTransformer.service.ts
@@ -0,0 +1,61 @@
+import { module } from 'angular';
+import type { IServerGroup } from '@spinnaker/core';
+
+import type { ICloudrunServerGroupCommand } from '../serverGroup/configure/serverGroupCommandBuilder.service';
+
+export class CloudrunV2ServerGroupTransformer {
+ public static $inject = ['$q'];
+ constructor(private $q: ng.IQService) {}
+
+ public normalizeServerGroup(serverGroup: IServerGroup): PromiseLike {
+ return this.$q.resolve(serverGroup);
+ }
+
+ public convertServerGroupCommandToDeployConfiguration(command: ICloudrunServerGroupCommand): any {
+ return new CloudrunDeployDescription(command);
+ }
+}
+
+export class CloudrunDeployDescription {
+ public cloudProvider = 'cloudrun';
+ public provider = 'cloudrun';
+ public credentials: string;
+ public account: string;
+ public application: string;
+ public stack?: string;
+ public freeFormDetails?: string;
+ public configFiles: string[];
+ public region: string;
+ public strategy?: string;
+ public type?: string;
+ public fromArtifact: boolean;
+ public configArtifacts: string[];
+ public strategyApplication?: string;
+ public strategyPipeline?: string;
+ public gitCredentialType: string;
+ public interestingHealthProviderNames: string[];
+ public sourceType: string;
+
+ constructor(command: ICloudrunServerGroupCommand) {
+ this.credentials = command.credentials;
+ this.account = command.credentials;
+ this.application = command.application;
+ this.stack = command.stack;
+ this.freeFormDetails = command.freeFormDetails;
+ this.region = command.region;
+ this.strategy = command.strategy;
+ this.type = command.type;
+ this.fromArtifact = command.fromArtifact;
+ this.gitCredentialType = command.gitCredentialType;
+ this.configFiles = command.configFiles;
+ this.sourceType = command.sourceType;
+ this.interestingHealthProviderNames = command.interestingHealthProviderNames || [];
+ this.configArtifacts = [];
+ }
+}
+
+export const CLOUDRUN_SERVER_GROUP_TRANSFORMER = 'spinnaker.cloudrun.serverGroup.transformer.service';
+module(CLOUDRUN_SERVER_GROUP_TRANSFORMER, []).service(
+ 'cloudrunV2ServerGroupTransformer',
+ CloudrunV2ServerGroupTransformer,
+);
diff --git a/packages/cloudrun/tsconfig.json b/packages/cloudrun/tsconfig.json
new file mode 100644
index 00000000000..4d7b8b545e8
--- /dev/null
+++ b/packages/cloudrun/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "../tsconfig.app.base.json",
+ "compilerOptions": {
+ "jsx": "react",
+ "outDir": "dist",
+ "rootDir": "./src"
+ },
+ "include": ["src/**/*.ts", "src/**/*.tsx"],
+ "exclude": ["**/*.spec.*"]
+}
diff --git a/scripts/buildModules.js b/scripts/buildModules.js
index bd458574b91..40f627d0557 100755
--- a/scripts/buildModules.js
+++ b/scripts/buildModules.js
@@ -23,6 +23,7 @@ async function buildModules() {
'appengine',
'azure',
'cloudfoundry',
+ 'cloudrun',
'docker',
'google',
'huaweicloud',
diff --git a/scripts/build_order.sh b/scripts/build_order.sh
index 89a69d5f0c2..c59e362b748 100755
--- a/scripts/build_order.sh
+++ b/scripts/build_order.sh
@@ -11,6 +11,7 @@ ModuleDeps () {
appengine) echo "core" ;;
azure) echo "core" ;;
cloudfoundry) echo "core" ;;
+ cloudrun) echo "core" ;;
core) echo "presentation";;
docker) echo "core" ;;
ecs) echo "amazon docker core" ;;
diff --git a/scripts/bumpPackage.js b/scripts/bumpPackage.js
index 1144e0f15e4..95cdd8cfd9b 100755
--- a/scripts/bumpPackage.js
+++ b/scripts/bumpPackage.js
@@ -22,6 +22,7 @@ const packages = [
'packages/appengine/',
'packages/azure/',
'packages/cloudfoundry/',
+ 'packages/cloudrun/',
'packages/core/',
'packages/docker/',
'packages/ecs/',
diff --git a/tsconfig.json b/tsconfig.json
index b5639f1baad..978da4e0ae8 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -57,7 +57,10 @@
"azure": ["azure/src"],
"@spinnaker/ecs": ["ecs/src"],
"ecs/*": ["ecs/src/*"],
- "ecs": ["ecs/src"]
+ "ecs": ["ecs/src"],
+ "@spinnaker/cloudrun": ["cloudrun/src"],
+ "cloudrun/*": ["cloudrun/src/*"],
+ "cloudrun": ["cloudrun/src"],
},
"pretty": true,
"removeComments": false,
From 05d09c080ab09282f0e85f6f81f81059f3d848fd Mon Sep 17 00:00:00 2001
From: Krzysztof Kotula
Date: Wed, 15 Mar 2023 13:04:30 +0100
Subject: [PATCH 23/38] chore(feature-flag): mj parent pipeline enabled by
default (#9954)
Co-authored-by: ovidiupopa07 <105648914+ovidiupopa07@users.noreply.github.com>
---
packages/app/src/settings.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/app/src/settings.js b/packages/app/src/settings.js
index 555c7a55434..c860d7b02a7 100644
--- a/packages/app/src/settings.js
+++ b/packages/app/src/settings.js
@@ -50,7 +50,7 @@ const managedServiceAccountsEnabled =
process.env.MANAGED_SERVICE_ACCOUNTS_ENABLED === 'true';
const managedResourcesEnabled =
import.meta.env.VITE_MANAGED_RESOURCES_ENABLED === 'true' || process.env.MANAGED_RESOURCES_ENABLED === 'true';
-const manualJudgmentParentPipelineEnabled = import.meta.env.MJ_PARENTPIPELINE_ENABLED === 'true' || false;
+const manualJudgmentParentPipelineEnabled = import.meta.env.MJ_PARENTPIPELINE_ENABLED !== 'false';
const onDemandClusterThreshold =
import.meta.env.VITE_ON_DEMAND_CLUSTER_THRESHOLD || process.env.ON_DEMAND_CLUSTER_THRESHOLD || '350';
const reduxLoggerEnabled = import.meta.env.VITE_REDUX_LOGGER === 'true' || process.env.REDUX_LOGGER === 'true';
From b9b2e7d2a6735e488e9e9da96bdb2865a5e174c4 Mon Sep 17 00:00:00 2001
From: Spinnaker Bot <87720302+spinnakerbot2@users.noreply.github.com>
Date: Tue, 21 Mar 2023 04:42:23 -1000
Subject: [PATCH 24/38] Publish packages to NPM (#9955)
* chore(publish): publish packages (e1b8d70d2d284790122392289c0f7aa7c53f613a)
- deck-app@2.5.0
* feat(peerdep-sync): Synchronize peerdependencies
* chore(publish): publish peerdeps (e1b8d70d2d284790122392289c0f7aa7c53f613a)
- @spinnaker/pluginsdk-peerdeps@0.10.0
---------
Co-authored-by: spinnakerbot
---
packages/app/CHANGELOG.md | 11 +++++++++++
packages/app/package.json | 2 +-
packages/pluginsdk-peerdeps/CHANGELOG.md | 11 +++++++++++
packages/pluginsdk-peerdeps/package.json | 2 +-
4 files changed, 24 insertions(+), 2 deletions(-)
diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md
index 27723834bdf..129a937a994 100644
--- a/packages/app/CHANGELOG.md
+++ b/packages/app/CHANGELOG.md
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [2.5.0](https://github.com/spinnaker/deck/compare/deck-app@2.4.3...deck-app@2.5.0) (2023-03-15)
+
+
+### Features
+
+* **provider/cloudrun:** Added cloudrun functionality to deck ([#9931](https://github.com/spinnaker/deck/issues/9931)) ([3611e95](https://github.com/spinnaker/deck/commit/3611e9520b2294454a8b5dddc85cb3190722ffc7))
+
+
+
+
+
## [2.4.3](https://github.com/spinnaker/deck/compare/deck-app@2.4.2...deck-app@2.4.3) (2023-02-20)
**Note:** Version bump only for package deck-app
diff --git a/packages/app/package.json b/packages/app/package.json
index 63b502f7f63..b82a5da517e 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -1,7 +1,7 @@
{
"name": "deck-app",
"private": true,
- "version": "2.4.3",
+ "version": "2.5.0",
"description": "",
"main": "index.js",
"scripts": {
diff --git a/packages/pluginsdk-peerdeps/CHANGELOG.md b/packages/pluginsdk-peerdeps/CHANGELOG.md
index 76664d66b07..6de56ead08d 100644
--- a/packages/pluginsdk-peerdeps/CHANGELOG.md
+++ b/packages/pluginsdk-peerdeps/CHANGELOG.md
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [0.10.0](https://github.com/spinnaker/deck/compare/@spinnaker/pluginsdk-peerdeps@0.9.0...@spinnaker/pluginsdk-peerdeps@0.10.0) (2023-03-15)
+
+
+### Features
+
+* **peerdep-sync:** Synchronize peerdependencies ([1eca783](https://github.com/spinnaker/deck/commit/1eca783ac57b894efc37c58a60122f2b5ce07b05))
+
+
+
+
+
# [0.9.0](https://github.com/spinnaker/deck/compare/@spinnaker/pluginsdk-peerdeps@0.8.0...@spinnaker/pluginsdk-peerdeps@0.9.0) (2023-02-20)
diff --git a/packages/pluginsdk-peerdeps/package.json b/packages/pluginsdk-peerdeps/package.json
index 930a4a38d37..eb9581ba6db 100644
--- a/packages/pluginsdk-peerdeps/package.json
+++ b/packages/pluginsdk-peerdeps/package.json
@@ -1,7 +1,7 @@
{
"name": "@spinnaker/pluginsdk-peerdeps",
"description": "Provides package dependencies to plugin developers",
- "version": "0.9.0",
+ "version": "0.10.0",
"license": "Apache-2.0",
"scripts": {
"temp": "./convert-peerdeps.js --from-peerdeps --input package.json --output package.temp.json",
From 8ee7582cb8a2c3076c7fa58af9ed3235c70b3c44 Mon Sep 17 00:00:00 2001
From: Yugandharkumar
Date: Mon, 26 Jun 2023 18:55:08 +0530
Subject: [PATCH 25/38] Update deck-oes.yml
---
.github/workflows/deck-oes.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/deck-oes.yml b/.github/workflows/deck-oes.yml
index b62a291d900..93ce76123e4 100644
--- a/.github/workflows/deck-oes.yml
+++ b/.github/workflows/deck-oes.yml
@@ -4,7 +4,7 @@ on:
workflow_call:
push:
branches:
- - OES-1.30.x-synch
+ - OES-1.30.x-synch-1.30.1-merge
env:
GRADLE_OPTS: -Dorg.gradle.daemon=false -Xmx6g -Xms6g
From 10f651a152abface01526029977da451785758ad Mon Sep 17 00:00:00 2001
From: Siddhu
Date: Tue, 9 May 2023 17:02:31 +0530
Subject: [PATCH 26/38] fix(ecs): VPC Subnet dropdown fix in ecs server group
creation.
---
.../configure/wizard/networking/Networking.tsx | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/packages/ecs/src/serverGroup/configure/wizard/networking/Networking.tsx b/packages/ecs/src/serverGroup/configure/wizard/networking/Networking.tsx
index 106ae1379c4..c38b41d2f55 100644
--- a/packages/ecs/src/serverGroup/configure/wizard/networking/Networking.tsx
+++ b/packages/ecs/src/serverGroup/configure/wizard/networking/Networking.tsx
@@ -80,8 +80,15 @@ export class EcsNetworking extends React.Component) => {
const updatedNetworkMode = newNetworkMode.value;
+ const cmd = this.props.command;
this.props.notifyAngular('networkMode', updatedNetworkMode);
- this.setState({ networkMode: updatedNetworkMode });
+ this.setState({
+ networkMode: updatedNetworkMode,
+ subnetTypesAvailable:
+ cmd.backingData && cmd.backingData.filtered && cmd.backingData.filtered.subnetTypes
+ ? cmd.backingData.filtered.subnetTypes
+ : [],
+ });
};
private updateSecurityGroups = (newSecurityGroups: Option) => {
From ff139a5aaa093b784771f6165017509c9c6a5184 Mon Sep 17 00:00:00 2001
From: Yugandharkumar
Date: Wed, 28 Jun 2023 14:11:27 +0530
Subject: [PATCH 27/38] Update deck-oes.yml
---
.github/workflows/deck-oes.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/deck-oes.yml b/.github/workflows/deck-oes.yml
index 93ce76123e4..a4806f58746 100644
--- a/.github/workflows/deck-oes.yml
+++ b/.github/workflows/deck-oes.yml
@@ -4,7 +4,7 @@ on:
workflow_call:
push:
branches:
- - OES-1.30.x-synch-1.30.1-merge
+ - OES-1.30.1
env:
GRADLE_OPTS: -Dorg.gradle.daemon=false -Xmx6g -Xms6g
From b6e066ee127519fbf02b3a47ecaaa392d12a2849 Mon Sep 17 00:00:00 2001
From: Yugandharkumar
Date: Tue, 8 Aug 2023 15:37:11 +0530
Subject: [PATCH 28/38] Update Preserve-commits.yml
---
.github/workflows/Preserve-commits.yml | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/Preserve-commits.yml b/.github/workflows/Preserve-commits.yml
index c96620c756e..c8051a6aee1 100644
--- a/.github/workflows/Preserve-commits.yml
+++ b/.github/workflows/Preserve-commits.yml
@@ -18,7 +18,8 @@ jobs:
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
- name: List files in the repository
run: |
- # ls ${{ github.workspace }}
+ # ls ${{ github.workspace }}
+ git clone https://github.com/${{ github.repository }}
git branch
touch Dev_commits
git config --global user.email "yugaa22@gmail.com"
@@ -26,5 +27,7 @@ jobs:
git log -1 | grep commit | awk '{print $2}' >> Dev_commits
git add .
git commit -m "Developper_commits"
+ git remote set-url origin https://${{ github.actor }}:$GITHUB_TOEKN@github.com/${{ github.repository }}
+
git push
- run: echo "🍏 This job's status is ${{ job.status }}."
From 0a23895a1e49be753eeab74a95a0a5a71509143a Mon Sep 17 00:00:00 2001
From: Yugandharkumar
Date: Tue, 8 Aug 2023 15:45:16 +0530
Subject: [PATCH 29/38] Update Preserve-commits.yml
---
.github/workflows/Preserve-commits.yml | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/Preserve-commits.yml b/.github/workflows/Preserve-commits.yml
index c8051a6aee1..3aa45583780 100644
--- a/.github/workflows/Preserve-commits.yml
+++ b/.github/workflows/Preserve-commits.yml
@@ -28,6 +28,8 @@ jobs:
git add .
git commit -m "Developper_commits"
git remote set-url origin https://${{ github.actor }}:$GITHUB_TOEKN@github.com/${{ github.repository }}
-
+
git push
+ env:
+ GITHUB_TOEKN: ${{ secrets.GIT_TOKEN }}
- run: echo "🍏 This job's status is ${{ job.status }}."
From e7aec245ad6554ab6c999b164c110002b192efda Mon Sep 17 00:00:00 2001
From: Yugandharkumar
Date: Wed, 9 Aug 2023 10:19:09 +0530
Subject: [PATCH 30/38] Update deck-oes.yml
---
.github/workflows/deck-oes.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/deck-oes.yml b/.github/workflows/deck-oes.yml
index a4806f58746..a603c01c5e5 100644
--- a/.github/workflows/deck-oes.yml
+++ b/.github/workflows/deck-oes.yml
@@ -29,7 +29,7 @@ jobs:
id: build_variables
run: |
echo ::set-output name=REPO::ubi8-deck-cve
- echo ::set-output name=VERSION::"$(git rev-parse --short HEAD)-$(date --utc +'%Y%m%d%H%M')"
+ echo ::set-output name=VERSION::"1.30.1$(date --utc +'%Y%m%d%H%M')"
- name: Login to Quay
uses: docker/login-action@v1
# use service account flow defined at: https://github.com/docker/login-action#service-account-based-authentication-1
From 94b8253c9057583b7a4137e2085b0d47d412780b Mon Sep 17 00:00:00 2001
From: Yugandharkumar
Date: Fri, 18 Aug 2023 12:16:04 +0530
Subject: [PATCH 31/38] Update deck-oes.yml
---
.github/workflows/deck-oes.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/deck-oes.yml b/.github/workflows/deck-oes.yml
index a603c01c5e5..6b1826b7fc2 100644
--- a/.github/workflows/deck-oes.yml
+++ b/.github/workflows/deck-oes.yml
@@ -29,7 +29,7 @@ jobs:
id: build_variables
run: |
echo ::set-output name=REPO::ubi8-deck-cve
- echo ::set-output name=VERSION::"1.30.1$(date --utc +'%Y%m%d%H%M')"
+ echo ::set-output name=VERSION::"1.30.1$(date --utc +'%Y%m%d')"
- name: Login to Quay
uses: docker/login-action@v1
# use service account flow defined at: https://github.com/docker/login-action#service-account-based-authentication-1
From f31e305ef204ba4d9430299fa5d610483c1b3acc Mon Sep 17 00:00:00 2001
From: Siddhu
Date: Tue, 18 Jul 2023 20:50:33 +0530
Subject: [PATCH 32/38] OP-19325 - Added new feature in Trigger spinnaker job
when Jenkins build is unstable
---
packages/core/src/help/help.contents.ts | 2 ++
.../config/triggers/baseBuild/BaseBuildTrigger.tsx | 13 ++++++++++++-
2 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/packages/core/src/help/help.contents.ts b/packages/core/src/help/help.contents.ts
index ea6772dd1ba..504a7dcb253 100644
--- a/packages/core/src/help/help.contents.ts
+++ b/packages/core/src/help/help.contents.ts
@@ -239,6 +239,8 @@ const helpContents: { [key: string]: string } = {
'Configures the cluster upon which this destroy operation will act. The target specifies what server group to resolve for the operation.
',
'pipeline.config.jenkins.trigger.propertyFile':
'(Optional) Configures the name to the Jenkins artifact file used to pass in properties to later stages in the Spinnaker pipeline. The contents of this file will now be available as a map under the trigger and accessible via trigger.properties . See Pipeline Expressions docs for more information.
',
+ 'pipeline.config.jenkins.trigger.unstableBuild':
+ 'If Jenkins reports the build status as UNSTABLE, Spinnaker will mark the build as SUCCEEDED and start execution of the pipeline.
',
'pipeline.config.jenkins.trigger.payloadConstraints': `(Optional, Requires Property File) When provided, only a build that contains a Property File with correct constraints will trigger this pipeline. For example, you could restrict the trigger to certain branches by placing the branch name in your Property File and adding a constraint with a key like "branch" and value "master".
The constraint values may be supplied as regex.
`,
'pipeline.config.jenkins.propertyFile':
diff --git a/packages/core/src/pipeline/config/triggers/baseBuild/BaseBuildTrigger.tsx b/packages/core/src/pipeline/config/triggers/baseBuild/BaseBuildTrigger.tsx
index bee6b644daf..994f638b98c 100644
--- a/packages/core/src/pipeline/config/triggers/baseBuild/BaseBuildTrigger.tsx
+++ b/packages/core/src/pipeline/config/triggers/baseBuild/BaseBuildTrigger.tsx
@@ -6,7 +6,7 @@ import { BuildServiceType, IgorService } from '../../../../ci/igor.service';
import type { IBuildTrigger } from '../../../../domain';
import { MapEditorInput } from '../../../../forms';
import { HelpField } from '../../../../help';
-import { FormikFormField, TextInput, useLatestPromise } from '../../../../presentation';
+import { CheckboxInput, FormikFormField, TextInput, useLatestPromise } from '../../../../presentation';
export interface IBaseBuildTriggerConfigProps {
formik: FormikProps;
@@ -80,6 +80,17 @@ export function BaseBuildTrigger(buildTriggerProps: IBaseBuildTriggerConfigProps
/>
)}
+ {type == 'jenkins' && (
+ }
+ input={(props) => (
+
+ )}
+ />
+ )}
+
Date: Mon, 17 Jul 2023 18:03:14 +0530
Subject: [PATCH 33/38] OP-19945- Implemented feature to hide and show cluster
level action based on Application permission.
---
packages/app/src/settings-local.js | 1 +
packages/core/src/config/settings.ts | 2 ++
.../serverGroup/details/details.controller.ts | 20 +++++++++++++++++++
.../src/serverGroup/details/details.html | 2 +-
4 files changed, 24 insertions(+), 1 deletion(-)
diff --git a/packages/app/src/settings-local.js b/packages/app/src/settings-local.js
index dc0a1b821af..2edbb83ea22 100644
--- a/packages/app/src/settings-local.js
+++ b/packages/app/src/settings-local.js
@@ -3,3 +3,4 @@
e.g.,
window.spinnakerSettings.defaultInstancePort = 8080;
*/
+window.spinnakerSettings.kubernetesAdHocInfraEditEnabled = true;
diff --git a/packages/core/src/config/settings.ts b/packages/core/src/config/settings.ts
index 255c18bd6a5..557e6cee3aa 100644
--- a/packages/core/src/config/settings.ts
+++ b/packages/core/src/config/settings.ts
@@ -149,6 +149,7 @@ export interface ISpinnakerSettings {
triggerTypes: string[];
useClassicFirewallLabels: boolean;
kubernetesAdHocInfraWritesEnabled: boolean;
+ kubernetesAdHocInfraEditEnabled: boolean;
changelogUrl: string;
}
@@ -158,6 +159,7 @@ export const SETTINGS: ISpinnakerSettings = (window as any).spinnakerSettings ||
SETTINGS.feature = SETTINGS.feature || {};
SETTINGS.feature.roscoMode = SETTINGS.feature.roscoMode ?? true;
SETTINGS.kubernetesAdHocInfraWritesEnabled = SETTINGS.kubernetesAdHocInfraWritesEnabled ?? true;
+SETTINGS.kubernetesAdHocInfraEditEnabled = SETTINGS.kubernetesAdHocInfraEditEnabled ?? false;
SETTINGS.analytics = SETTINGS.analytics || {};
SETTINGS.providers = SETTINGS.providers || {};
SETTINGS.defaultTimeZone = SETTINGS.defaultTimeZone || 'America/Los_Angeles';
diff --git a/packages/kubernetes/src/serverGroup/details/details.controller.ts b/packages/kubernetes/src/serverGroup/details/details.controller.ts
index 7103217a042..51838bb98c7 100644
--- a/packages/kubernetes/src/serverGroup/details/details.controller.ts
+++ b/packages/kubernetes/src/serverGroup/details/details.controller.ts
@@ -5,6 +5,7 @@ import type { IModalService } from 'angular-ui-bootstrap';
import type { Application, IManifest, IOwnerOption, IServerGroup } from '@spinnaker/core';
import {
+ AuthenticationService,
ClusterTargetBuilder,
ConfirmationModalService,
ManifestReader,
@@ -64,6 +65,25 @@ class KubernetesServerGroupDetailsController implements IController {
}
}
+ public isEditEnabled(): boolean {
+ const authenticatedUser = AuthenticationService.getAuthenticatedUser();
+ const applicationAttr = this.app.attributes;
+ const isExist = (arr1: string[], arr2: string[]) => {
+ return arr1?.some((v) => arr2?.includes(v));
+ };
+ const isWriteEnabled = () => {
+ if (authenticatedUser.name !== applicationAttr.user) {
+ return (
+ isExist(applicationAttr.permissions?.WRITE, authenticatedUser.roles) ||
+ isExist(applicationAttr.permissions?.EXECUTE, authenticatedUser.roles)
+ );
+ } else {
+ return true;
+ }
+ };
+ return SETTINGS.kubernetesAdHocInfraEditEnabled ? isWriteEnabled() : true;
+ }
+
private ownerIsController(ownerReference: any): boolean {
return ownerReference.hasOwnProperty('controller') && ownerReference.controller === true;
}
diff --git a/packages/kubernetes/src/serverGroup/details/details.html b/packages/kubernetes/src/serverGroup/details/details.html
index 5765be06fdb..f2e614f8396 100644
--- a/packages/kubernetes/src/serverGroup/details/details.html
+++ b/packages/kubernetes/src/serverGroup/details/details.html
@@ -37,7 +37,7 @@
-
+
{{ctrl.serverGroup.kind | robotToHuman}} Actions
From b3cba5645f5179fae61e7ced20e7ad08dfaf91eb Mon Sep 17 00:00:00 2001
From: Siddhu
Date: Tue, 18 Jul 2023 10:49:11 +0530
Subject: [PATCH 34/38] OP-19945 - Incorporated suggested changes.
---
.../src/serverGroup/details/details.controller.ts | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/packages/kubernetes/src/serverGroup/details/details.controller.ts b/packages/kubernetes/src/serverGroup/details/details.controller.ts
index 51838bb98c7..ddbee64b34c 100644
--- a/packages/kubernetes/src/serverGroup/details/details.controller.ts
+++ b/packages/kubernetes/src/serverGroup/details/details.controller.ts
@@ -72,11 +72,8 @@ class KubernetesServerGroupDetailsController implements IController {
return arr1?.some((v) => arr2?.includes(v));
};
const isWriteEnabled = () => {
- if (authenticatedUser.name !== applicationAttr.user) {
- return (
- isExist(applicationAttr.permissions?.WRITE, authenticatedUser.roles) ||
- isExist(applicationAttr.permissions?.EXECUTE, authenticatedUser.roles)
- );
+ if (authenticatedUser.name !== applicationAttr.user && applicationAttr.permissions) {
+ return isExist(applicationAttr.permissions?.WRITE, authenticatedUser.roles);
} else {
return true;
}
From f38a84a2be9b87f6117e2de4af9bb99a9f7dd654 Mon Sep 17 00:00:00 2001
From: Rajinderpal Singh Siddhu
<61963157+siddhu-opsmx@users.noreply.github.com>
Date: Tue, 1 Aug 2023 17:51:12 +0530
Subject: [PATCH 35/38] feature/OP-19945- Updated the code changes with
Deployment as well along with replicaset (#78)
---
.../details/details.controller.ts | 18 +++++++++++++++++-
.../serverGroupManager/details/details.html | 2 +-
2 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/packages/kubernetes/src/serverGroupManager/details/details.controller.ts b/packages/kubernetes/src/serverGroupManager/details/details.controller.ts
index 658ee68b448..49642172228 100644
--- a/packages/kubernetes/src/serverGroupManager/details/details.controller.ts
+++ b/packages/kubernetes/src/serverGroupManager/details/details.controller.ts
@@ -11,7 +11,7 @@ import type {
IServerGroupManager,
IServerGroupManagerStateParams,
} from '@spinnaker/core';
-import { ClusterTargetBuilder, ManifestReader, NameUtils, SETTINGS } from '@spinnaker/core';
+import { AuthenticationService, ClusterTargetBuilder, ManifestReader, NameUtils, SETTINGS } from '@spinnaker/core';
import type { IKubernetesServerGroupManager } from '../../interfaces';
import { KubernetesManifestCommandBuilder } from '../../manifest/manifestCommandBuilder.service';
@@ -43,6 +43,22 @@ class KubernetesServerGroupManagerDetailsController implements IController {
this.$scope.isDisabled = !SETTINGS.kubernetesAdHocInfraWritesEnabled;
}
+ public isEditEnabled(): boolean {
+ const authenticatedUser = AuthenticationService.getAuthenticatedUser();
+ const applicationAttr = this.app.attributes;
+ const isExist = (arr1: string[], arr2: string[]) => {
+ return arr1?.some((v) => arr2?.includes(v));
+ };
+ const isWriteEnabled = () => {
+ if (authenticatedUser.name !== applicationAttr.user && applicationAttr.permissions) {
+ return isExist(applicationAttr.permissions?.WRITE, authenticatedUser.roles);
+ } else {
+ return true;
+ }
+ };
+ return SETTINGS.kubernetesAdHocInfraEditEnabled ? isWriteEnabled() : true;
+ }
+
public pauseRolloutServerGroupManager(): void {
this.$uibModal.open({
templateUrl: require('../../manifest/rollout/pause.html'),
diff --git a/packages/kubernetes/src/serverGroupManager/details/details.html b/packages/kubernetes/src/serverGroupManager/details/details.html
index a84e431cfeb..1b9674783e9 100644
--- a/packages/kubernetes/src/serverGroupManager/details/details.html
+++ b/packages/kubernetes/src/serverGroupManager/details/details.html
@@ -37,7 +37,7 @@
-
+
{{ctrl.serverGroupManager.kind | robotToHuman}} Actions
From e043909501b4ec58a317d5eae20d6cf4ff150362 Mon Sep 17 00:00:00 2001
From: Rajinderpal Singh Siddhu
<61963157+siddhu-opsmx@users.noreply.github.com>
Date: Thu, 14 Sep 2023 13:20:00 +0530
Subject: [PATCH 36/38] ECS changes related to Disable server group tab (#79)
---
packages/core/src/config/settings.ts | 2 ++
.../serverGroupDetails.ecs.controller.js | 18 ++++++++++++++++++
.../details/serverGroupDetails.html | 6 +++++-
3 files changed, 25 insertions(+), 1 deletion(-)
diff --git a/packages/core/src/config/settings.ts b/packages/core/src/config/settings.ts
index 557e6cee3aa..0d14ef73225 100644
--- a/packages/core/src/config/settings.ts
+++ b/packages/core/src/config/settings.ts
@@ -151,6 +151,7 @@ export interface ISpinnakerSettings {
kubernetesAdHocInfraWritesEnabled: boolean;
kubernetesAdHocInfraEditEnabled: boolean;
changelogUrl: string;
+ ecsAdHocInfraEditEnabled: boolean;
}
export const SETTINGS: ISpinnakerSettings = (window as any).spinnakerSettings || {};
@@ -160,6 +161,7 @@ SETTINGS.feature = SETTINGS.feature || {};
SETTINGS.feature.roscoMode = SETTINGS.feature.roscoMode ?? true;
SETTINGS.kubernetesAdHocInfraWritesEnabled = SETTINGS.kubernetesAdHocInfraWritesEnabled ?? true;
SETTINGS.kubernetesAdHocInfraEditEnabled = SETTINGS.kubernetesAdHocInfraEditEnabled ?? false;
+SETTINGS.ecsAdHocInfraEditEnabled = SETTINGS.ecsAdHocInfraEditEnabled ?? false;
SETTINGS.analytics = SETTINGS.analytics || {};
SETTINGS.providers = SETTINGS.providers || {};
SETTINGS.defaultTimeZone = SETTINGS.defaultTimeZone || 'America/Los_Angeles';
diff --git a/packages/ecs/src/serverGroup/details/serverGroupDetails.ecs.controller.js b/packages/ecs/src/serverGroup/details/serverGroupDetails.ecs.controller.js
index 84672977828..3408eead754 100644
--- a/packages/ecs/src/serverGroup/details/serverGroupDetails.ecs.controller.js
+++ b/packages/ecs/src/serverGroup/details/serverGroupDetails.ecs.controller.js
@@ -6,12 +6,14 @@ import { chain, filter, find, has, isEmpty } from 'lodash';
import {
AccountService,
+ AuthenticationService,
ConfirmationModalService,
FirewallLabels,
OVERRIDE_REGISTRY,
SERVER_GROUP_WRITER,
ServerGroupReader,
ServerGroupWarningMessageService,
+ SETTINGS,
SubnetReader,
} from '@spinnaker/core';
@@ -362,5 +364,21 @@ angular
serverGroup.accountDetails = details;
});
};
+
+ this.isEditEnabled = () => {
+ const authenticatedUser = AuthenticationService.getAuthenticatedUser();
+ const applicationAttr = this.application.attributes;
+ const isExist = (arr1, arr2) => {
+ return arr1?.some((v) => arr2?.includes(v));
+ };
+ const isWriteEnabled = () => {
+ if (authenticatedUser.name !== applicationAttr.user && applicationAttr.permissions) {
+ return isExist(applicationAttr.permissions?.WRITE, authenticatedUser.roles);
+ } else {
+ return true;
+ }
+ };
+ return SETTINGS.ecsAdHocInfraEditEnabled ? isWriteEnabled() : true;
+ };
},
]);
diff --git a/packages/ecs/src/serverGroup/details/serverGroupDetails.html b/packages/ecs/src/serverGroup/details/serverGroupDetails.html
index b9c9323938d..5032e5e5ad0 100644
--- a/packages/ecs/src/serverGroup/details/serverGroupDetails.html
+++ b/packages/ecs/src/serverGroup/details/serverGroupDetails.html
@@ -34,7 +34,11 @@
-
+
Server Group Actions
From 494dcd67afd9c3661ad0aa668e7550e6901853b2 Mon Sep 17 00:00:00 2001
From: Rajinderpal Singh Siddhu
<61963157+siddhu-opsmx@users.noreply.github.com>
Date: Tue, 19 Sep 2023 10:03:07 +0530
Subject: [PATCH 37/38] AWS and ECS changes according to new requirement And
fixed build related issue (#83)
---
.../details/AmazonServerGroupActions.tsx | 2 +-
packages/core/src/config/settings.ts | 4 ++--
.../details/serverGroupDetails.ecs.controller.js | 14 +-------------
.../cypress/integration/ecs/clusters_list.spec.js | 15 ---------------
4 files changed, 4 insertions(+), 31 deletions(-)
diff --git a/packages/amazon/src/serverGroup/details/AmazonServerGroupActions.tsx b/packages/amazon/src/serverGroup/details/AmazonServerGroupActions.tsx
index bdec5a25984..60768290ea5 100644
--- a/packages/amazon/src/serverGroup/details/AmazonServerGroupActions.tsx
+++ b/packages/amazon/src/serverGroup/details/AmazonServerGroupActions.tsx
@@ -287,7 +287,7 @@ export class AmazonServerGroupActions extends React.Component
- {AWSProviderSettings.adHocInfraWritesEnabled && (
+ {AWSProviderSettings.adHocInfraWritesEnabled && SETTINGS.adHocInfraEditEnabled && (
Server Group Actions
diff --git a/packages/core/src/config/settings.ts b/packages/core/src/config/settings.ts
index 0d14ef73225..e62cc4c329b 100644
--- a/packages/core/src/config/settings.ts
+++ b/packages/core/src/config/settings.ts
@@ -151,7 +151,7 @@ export interface ISpinnakerSettings {
kubernetesAdHocInfraWritesEnabled: boolean;
kubernetesAdHocInfraEditEnabled: boolean;
changelogUrl: string;
- ecsAdHocInfraEditEnabled: boolean;
+ adHocInfraEditEnabled: boolean;
}
export const SETTINGS: ISpinnakerSettings = (window as any).spinnakerSettings || {};
@@ -161,7 +161,7 @@ SETTINGS.feature = SETTINGS.feature || {};
SETTINGS.feature.roscoMode = SETTINGS.feature.roscoMode ?? true;
SETTINGS.kubernetesAdHocInfraWritesEnabled = SETTINGS.kubernetesAdHocInfraWritesEnabled ?? true;
SETTINGS.kubernetesAdHocInfraEditEnabled = SETTINGS.kubernetesAdHocInfraEditEnabled ?? false;
-SETTINGS.ecsAdHocInfraEditEnabled = SETTINGS.ecsAdHocInfraEditEnabled ?? false;
+SETTINGS.adHocInfraEditEnabled = SETTINGS.adHocInfraEditEnabled ?? true;
SETTINGS.analytics = SETTINGS.analytics || {};
SETTINGS.providers = SETTINGS.providers || {};
SETTINGS.defaultTimeZone = SETTINGS.defaultTimeZone || 'America/Los_Angeles';
diff --git a/packages/ecs/src/serverGroup/details/serverGroupDetails.ecs.controller.js b/packages/ecs/src/serverGroup/details/serverGroupDetails.ecs.controller.js
index 3408eead754..a36385eda2b 100644
--- a/packages/ecs/src/serverGroup/details/serverGroupDetails.ecs.controller.js
+++ b/packages/ecs/src/serverGroup/details/serverGroupDetails.ecs.controller.js
@@ -366,19 +366,7 @@ angular
};
this.isEditEnabled = () => {
- const authenticatedUser = AuthenticationService.getAuthenticatedUser();
- const applicationAttr = this.application.attributes;
- const isExist = (arr1, arr2) => {
- return arr1?.some((v) => arr2?.includes(v));
- };
- const isWriteEnabled = () => {
- if (authenticatedUser.name !== applicationAttr.user && applicationAttr.permissions) {
- return isExist(applicationAttr.permissions?.WRITE, authenticatedUser.roles);
- } else {
- return true;
- }
- };
- return SETTINGS.ecsAdHocInfraEditEnabled ? isWriteEnabled() : true;
+ return SETTINGS.adHocInfraEditEnabled;
};
},
]);
diff --git a/test/functional/cypress/integration/ecs/clusters_list.spec.js b/test/functional/cypress/integration/ecs/clusters_list.spec.js
index 79c81ec3abd..34c20ee3861 100644
--- a/test/functional/cypress/integration/ecs/clusters_list.spec.js
+++ b/test/functional/cypress/integration/ecs/clusters_list.spec.js
@@ -29,16 +29,6 @@ describe('Amazon ECS: aws-prod-ecsdemo cluster', () => {
cy.get('.sub-group:contains("aws-prod-ecsdemo")')
.find('.server-group:contains("v000")')
.click({ force: true });
-
- cy.get('.btn:contains("Server Group Actions")')
- .click()
- .get('.dropdown-menu')
- .get('.ng-scope')
- .should('contain.text', 'Rollback');
-
- cy.get('a:contains("Rollback")').click({ force: true });
-
- cy.get('.modal-title').should('contain.text', 'Rollback aws-prod-ecsdemo');
});
it('shows stored details view and ECS server group actions', () => {
@@ -47,11 +37,6 @@ describe('Amazon ECS: aws-prod-ecsdemo cluster', () => {
cy.get('.sub-group:contains("aws-prod-ecsdemo")')
.find('.server-group:contains("v000")')
.click({ force: true });
-
- cy.get('.btn:contains("Server Group Actions")')
- .click()
- .get('a:contains("Rollback")')
- .click();
});
it('shows stored instance details view action', () => {
From 2ebc9b6009e2a98e181fcfd86145b3ce40a7e6ea Mon Sep 17 00:00:00 2001
From: Hanumesh Kumar
Date: Wed, 1 Nov 2023 12:24:37 +0530
Subject: [PATCH 38/38] Update serverGroupDetails.ecs.controller.js
---
.../src/serverGroup/details/serverGroupDetails.ecs.controller.js | 1 -
1 file changed, 1 deletion(-)
diff --git a/packages/ecs/src/serverGroup/details/serverGroupDetails.ecs.controller.js b/packages/ecs/src/serverGroup/details/serverGroupDetails.ecs.controller.js
index a36385eda2b..eee386e938e 100644
--- a/packages/ecs/src/serverGroup/details/serverGroupDetails.ecs.controller.js
+++ b/packages/ecs/src/serverGroup/details/serverGroupDetails.ecs.controller.js
@@ -6,7 +6,6 @@ import { chain, filter, find, has, isEmpty } from 'lodash';
import {
AccountService,
- AuthenticationService,
ConfirmationModalService,
FirewallLabels,
OVERRIDE_REGISTRY,