diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index e342a140b76..00000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,15 +0,0 @@ -version: 2 -updates: - - package-ecosystem: 'npm' - directory: '/' - schedule: - interval: daily - allow: - - dependency-name: '@spinnaker/kayenta' - - dependency-name: '@spinnaker/styleguide' - - dependency-name: '@spinnaker/presentation' - # Maintain dependencies for GitHub Actions - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "monthly" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 8e223f1e370..00000000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,99 +0,0 @@ -name: Branch Build - -on: - push: - branches: - - master - - release-* - -env: - GRADLE_OPTS: -Dorg.gradle.daemon=false -Xmx2g -Xms2g - CONTAINER_REGISTRY: us-docker.pkg.dev/spinnaker-community/docker - NODE_VERSION: 12.16.0 - -permissions: - contents: read - -jobs: - branch-build: - # Only run this on repositories in the 'spinnaker' org, not on forks. - if: startsWith(github.repository, 'spinnaker/') - 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-node@v1 - with: - node-version: ${{ env.NODE_VERSION }} - - - name: Get yarn cache - id: yarn-cache - run: echo "::set-output name=dir::$(yarn cache dir)" - - - uses: actions/cache@v1 - with: - path: ${{ steps.yarn-cache.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - - uses: actions/setup-java@v2 - with: - java-version: 11 - distribution: 'zulu' - cache: 'gradle' - - - name: Prepare build variables - id: build_variables - run: | - echo ::set-output name=REPO::${GITHUB_REPOSITORY##*/} - echo ::set-output name=VERSION::"$(git describe --tags --abbrev=0 --match="v[0-9]*" | cut -c2-)-dev-${GITHUB_REF_NAME}-$(git rev-parse --short HEAD)-$(date --utc +'%Y%m%d%H%M')" - - - name: Build - env: - ORG_GRADLE_PROJECT_version: ${{ steps.build_variables.outputs.VERSION }} - run: ./gradlew build --stacktrace - - - name: Login to GAR - # Only run this on repositories in the 'spinnaker' org, not on forks. - if: startsWith(github.repository, 'spinnaker/') - uses: docker/login-action@v1 - # use service account flow defined at: https://github.com/docker/login-action#service-account-based-authentication-1 - with: - registry: us-docker.pkg.dev - username: _json_key - password: ${{ secrets.GAR_JSON_KEY }} - - - name: Build and publish slim container image - # Only run this on repositories in the 'spinnaker' org, not on forks. - if: startsWith(github.repository, 'spinnaker/') - uses: docker/build-push-action@v3 - with: - context: . - file: Dockerfile.slim - platforms: linux/amd64,linux/arm64 - push: true - tags: | - "${{ env.CONTAINER_REGISTRY }}/${{ steps.build_variables.outputs.REPO }}:${{ github.ref_name }}-latest-unvalidated" - "${{ env.CONTAINER_REGISTRY }}/${{ steps.build_variables.outputs.REPO }}:${{ steps.build_variables.outputs.VERSION }}-unvalidated" - "${{ env.CONTAINER_REGISTRY }}/${{ steps.build_variables.outputs.REPO }}:${{ github.ref_name }}-latest-unvalidated-slim" - "${{ env.CONTAINER_REGISTRY }}/${{ steps.build_variables.outputs.REPO }}:${{ steps.build_variables.outputs.VERSION }}-unvalidated-slim" - - - name: Build and publish ubuntu container image - # Only run this on repositories in the 'spinnaker' org, not on forks. - if: startsWith(github.repository, 'spinnaker/') - uses: docker/build-push-action@v3 - with: - context: . - file: Dockerfile.ubuntu - platforms: linux/amd64,linux/arm64 - push: true - tags: | - "${{ env.CONTAINER_REGISTRY }}/${{ steps.build_variables.outputs.REPO }}:${{ github.ref_name }}-latest-unvalidated-ubuntu" - "${{ env.CONTAINER_REGISTRY }}/${{ steps.build_variables.outputs.REPO }}:${{ steps.build_variables.outputs.VERSION }}-unvalidated-ubuntu" diff --git a/.github/workflows/deck-oes.yml b/.github/workflows/deck-oes.yml index b53ecf3f9a4..8bb474fa3a7 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.1 + - OES-1.33.x env: GRADLE_OPTS: -Dorg.gradle.daemon=false -Xmx6g -Xms6g @@ -29,8 +29,8 @@ 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')" - echo ::set-output name=VERSION::"1.30.1" + #echo ::set-output name=VERSION::"1.33.x$(date --utc +'%Y%m%d')" + echo ::set-output name=VERSION::"1.33.x" echo "::set-output name=GITHASH::$(git rev-parse --short HEAD)" echo "::set-output name=BUILDDATE::$(date -u +"%Y%m%d%H%M")" - name: Login to Quay diff --git a/.github/workflows/package-bump-pr.yml b/.github/workflows/package-bump-pr.yml deleted file mode 100644 index add66d84e12..00000000000 --- a/.github/workflows/package-bump-pr.yml +++ /dev/null @@ -1,118 +0,0 @@ -name: Create package bump PR - -concurrency: - group: create-package-bump-pr - cancel-in-progress: true - -on: - push: - branches: - - 'master' - tags: - - '@spinnaker/*' - -env: - NODE_VERSION: 12.16.0 - -jobs: - build: - # Only run this on repositories in the 'spinnaker' org, not on forks. - if: startsWith(github.repository, 'spinnaker/') - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - ref: master - - - name: git - configure commit user - run: | - git config user.name spinnakerbot - git config user.email spinnakerbot@spinnaker.io - git checkout master - - - uses: actions/setup-node@v1 - with: - node-version: ${{ env.NODE_VERSION }} - - - name: yarn - get cache dir - id: yarn-cache - run: echo "::set-output name=dir::$(yarn cache dir)" - - - uses: actions/cache@v1 - with: - path: ${{ steps.yarn-cache.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - - name: yarn - Install Dependencies - run: yarn --frozen-lockfile - - - name: lerna - Bump packages - id: lerna_bump - run: | - scripts/gha_bump_packages.sh - env: - COMMIT_SHA: ${{ github.sha }} - - name: gha - get bumped packages - id: bumps - run: | - scripts/gha_output_bumped_packages.sh - scripts/gha_output_changelog.sh - env: - PACKAGE_BUMP_COMMIT_HASH: ${{ steps.lerna_bump.outputs.packageBumpCommitHash }} - PEERDEP_BUMP_COMMIT_HASH: ${{ steps.lerna_bump.outputs.peerdepBumpCommitHash }} - - name: Create Pull Request - id: createpullrequest - uses: peter-evans/create-pull-request@v3 - with: - token: '${{ secrets.SPINNAKERBOT_PERSONAL_ACCESS_TOKEN }}' - commit-message: 'chore(package): Publish ${{ steps.bumps.outputs.bumps }}' - title: 'Publish packages to NPM' - labels: publish - body: | - ### This PR bumps the version(s) of all Deck package(s) that have unpublished changes. - - ${{ steps.bumps.outputs.changelog }} - - --- - - This PR bumps each package to the next semver version (patch/minor/major) based on the commit messages of unpublished changes using [Conventional Commits](https://conventionalcommits.org). - - - fix: patch release - - feat: minor release - - BREAKING CHANGE: major release - - It also updates dependency versions in other packages in the monorepo which depend on the bumped package(s). - - After this PR is merged, Github Actions will publish any bumped packages to the NPM registry. - - _Auto-generated by `.github/workflows/package-bump-pr.yml`_ - - - name: Close package bump due to no changes - if: ${{ steps.lerna_bump.outputs.packageBumpCommitHash == '' && steps.createpullrequest.outputs.pull-request-number != '' }} - uses: actions/github-script@0.9.0 - with: - github-token: '${{ secrets.SPINNAKERBOT_PERSONAL_ACCESS_TOKEN }}' - script: | - const { owner, repo } = context.repo; - const pull_number = ${{ steps.createpullrequest.outputs.pull-request-number }}; - await github.pulls.update({ owner, repo, pull_number, state: 'closed' }); - - - name: Approve package bump - if: ${{ steps.lerna_bump.outputs.packageBumpCommitHash != '' && steps.createpullrequest.outputs.pull-request-number != '' }} - uses: actions/github-script@0.9.0 - with: - github-token: '${{ secrets.SPINNAKERBOT_TOKEN }}' - script: | - const { owner, repo } = context.repo; - const pull_number = ${{ steps.createpullrequest.outputs.pull-request-number }}; - const users = ['spinnakerbot', 'spinnakerbot2']; - - const reviews = await github.pulls.listReviews({ owner, repo, pull_number }); - const approved = reviews.data.some((review) => users.includes(review.user.login) && review.state == 'APPROVED'); - - if (!approved) { - await github.pulls.createReview({ owner, repo, pull_number, event: 'APPROVE' }); - } diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml deleted file mode 100644 index 068486d6bae..00000000000 --- a/.github/workflows/pr.yml +++ /dev/null @@ -1,77 +0,0 @@ -name: PR Build - -on: [ pull_request ] - -env: - GRADLE_OPTS: -Dorg.gradle.daemon=false -Xmx2g -Xms2g - CONTAINER_REGISTRY: us-docker.pkg.dev/spinnaker-community/docker - NODE_VERSION: 12.16.0 - -permissions: - contents: read - -jobs: - 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-node@v1 - with: - node-version: ${{ env.NODE_VERSION }} - - - name: Get yarn cache - id: yarn-cache - run: echo "::set-output name=dir::$(yarn cache dir)" - - - uses: actions/cache@v1 - with: - path: ${{ steps.yarn-cache.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - - uses: actions/setup-java@v2 - with: - java-version: 11 - distribution: 'zulu' - cache: 'gradle' - - - name: Prepare build variables - id: build_variables - run: | - echo ::set-output name=REPO::${GITHUB_REPOSITORY##*/} - echo ::set-output name=VERSION::"$(git describe --tags --abbrev=0 --match="v[0-9]*" | cut -c2-)-dev-pr-$(git rev-parse --short HEAD)-$(date --utc +'%Y%m%d%H%M')" - - - name: Build - env: - ORG_GRADLE_PROJECT_version: ${{ steps.build_variables.outputs.VERSION }} - run: ./gradlew build - - - name: Build slim container image - uses: docker/build-push-action@v3 - with: - context: . - file: Dockerfile.slim - platforms: linux/amd64,linux/arm64 - tags: | - "${{ env.CONTAINER_REGISTRY }}/${{ steps.build_variables.outputs.REPO }}:latest" - "${{ env.CONTAINER_REGISTRY }}/${{ steps.build_variables.outputs.REPO }}:${{ steps.build_variables.outputs.VERSION }}" - "${{ env.CONTAINER_REGISTRY }}/${{ steps.build_variables.outputs.REPO }}:latest-slim" - "${{ env.CONTAINER_REGISTRY }}/${{ steps.build_variables.outputs.REPO }}:${{ steps.build_variables.outputs.VERSION }}-slim" - - - name: Build ubuntu container image - uses: docker/build-push-action@v3 - with: - context: . - file: Dockerfile.ubuntu - platforms: linux/amd64,linux/arm64 - tags: | - "${{ env.CONTAINER_REGISTRY }}/${{ steps.build_variables.outputs.REPO }}:latest-ubuntu" - "${{ env.CONTAINER_REGISTRY }}/${{ steps.build_variables.outputs.REPO }}:${{ steps.build_variables.outputs.VERSION }}-ubuntu" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d734c9a0718..e79fcc03523 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -3,12 +3,12 @@ name: Publish packages to NPM on: push: branches: - - 'master' + - 'OES-1.33.x' paths: - 'packages/*/package.json' env: - NODE_VERSION: 12.16.0 + NODE_VERSION: 14.21.3 permissions: contents: read @@ -20,12 +20,12 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 token: '${{ secrets.SPINNAKERBOT_PERSONAL_ACCESS_TOKEN }}' - - uses: actions/setup-node@v1 + - uses: actions/setup-node@v3 with: node-version: ${{ env.NODE_VERSION }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 628b8c68e2e..00000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,139 +0,0 @@ -name: Release - -on: - push: - tags: - - "v[0-9]+.[0-9]+.[0-9]+" - - "v[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+" - -env: - GRADLE_OPTS: -Dorg.gradle.daemon=false -Xmx2g -Xms2g - CONTAINER_REGISTRY: us-docker.pkg.dev/spinnaker-community/docker - NODE_VERSION: 12.16.0 - -jobs: - release: - 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-node@v1 - with: - node-version: ${{ env.NODE_VERSION }} - - - name: Get yarn cache - id: yarn-cache - run: echo "::set-output name=dir::$(yarn cache dir)" - - - uses: actions/cache@v1 - with: - path: ${{ steps.yarn-cache.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - - uses: actions/setup-java@v2 - with: - java-version: 11 - distribution: 'zulu' - cache: 'gradle' - - name: Assemble release info - id: release_info - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - . .github/workflows/release_info.sh ${{ github.event.repository.full_name }} - echo ::set-output name=CHANGELOG::$(echo -e "${CHANGELOG}") - echo ::set-output name=SKIP_RELEASE::${SKIP_RELEASE} - echo ::set-output name=IS_CANDIDATE::${IS_CANDIDATE} - echo ::set-output name=RELEASE_VERSION::${RELEASE_VERSION} - - name: Prepare build variables - id: build_variables - run: | - echo ::set-output name=REPO::${GITHUB_REPOSITORY##*/} - echo ::set-output name=VERSION::"$(git rev-parse --short HEAD)-$(date --utc +'%Y%m%d%H%M')" - - name: Release build - env: - ORG_GRADLE_PROJECT_version: ${{ steps.release_info.outputs.RELEASE_VERSION }} - ORG_GRADLE_PROJECT_nexusPublishEnabled: true - ORG_GRADLE_PROJECT_nexusUsername: ${{ secrets.NEXUS_USERNAME }} - ORG_GRADLE_PROJECT_nexusPassword: ${{ secrets.NEXUS_PASSWORD }} - ORG_GRADLE_PROJECT_nexusPgpSigningKey: ${{ secrets.NEXUS_PGP_SIGNING_KEY }} - ORG_GRADLE_PROJECT_nexusPgpSigningPassword: ${{ secrets.NEXUS_PGP_SIGNING_PASSWORD }} - run: | - ./gradlew --info build - - name: Publish apt packages to Google Artifact Registry - env: - ORG_GRADLE_PROJECT_version: ${{ steps.release_info.outputs.RELEASE_VERSION }} - ORG_GRADLE_PROJECT_artifactRegistryPublishEnabled: true - GAR_JSON_KEY: ${{ secrets.GAR_JSON_KEY }} - run: | - ./gradlew --info publishDebToArtifactRegistry - - name: Login to Google Cloud - # Only run this on repositories in the 'spinnaker' org, not on forks. - if: startsWith(github.repository, 'spinnaker/') - uses: 'google-github-actions/auth@v0' - # use service account flow defined at: https://github.com/google-github-actions/upload-cloud-storage#authenticating-via-service-account-key-json - with: - credentials_json: '${{ secrets.GAR_JSON_KEY }}' - - name: Upload halconfig profiles to GCS - # https://console.cloud.google.com/storage/browser/halconfig - # Only run this on repositories in the 'spinnaker' org, not on forks. - if: startsWith(github.repository, 'spinnaker/') - uses: 'google-github-actions/upload-cloud-storage@v0' - with: - path: 'halconfig/' - destination: 'halconfig/${{ steps.build_variables.outputs.REPO }}/${{ steps.release_info.outputs.RELEASE_VERSION }}' - parent: false - - name: Login to GAR - # Only run this on repositories in the 'spinnaker' org, not on forks. - if: startsWith(github.repository, 'spinnaker/') - uses: docker/login-action@v1 - # use service account flow defined at: https://github.com/docker/login-action#service-account-based-authentication-1 - with: - registry: us-docker.pkg.dev - username: _json_key - password: ${{ secrets.GAR_JSON_KEY }} - - name: Build and publish slim container image - # Only run this on repositories in the 'spinnaker' org, not on forks. - if: startsWith(github.repository, 'spinnaker/') - uses: docker/build-push-action@v3 - with: - context: . - file: Dockerfile.slim - platforms: linux/amd64,linux/arm64 - push: true - tags: | - "${{ env.CONTAINER_REGISTRY }}/${{ steps.build_variables.outputs.REPO }}:${{ steps.release_info.outputs.RELEASE_VERSION }}-unvalidated" - "${{ env.CONTAINER_REGISTRY }}/${{ steps.build_variables.outputs.REPO }}:${{ steps.release_info.outputs.RELEASE_VERSION }}-unvalidated-slim" - "${{ env.CONTAINER_REGISTRY }}/${{ steps.build_variables.outputs.REPO }}:${{ steps.release_info.outputs.RELEASE_VERSION }}-${{ steps.build_variables.outputs.VERSION }}-unvalidated-slim" - - name: Build and publish ubuntu container image - # Only run this on repositories in the 'spinnaker' org, not on forks. - if: startsWith(github.repository, 'spinnaker/') - uses: docker/build-push-action@v3 - with: - context: . - file: Dockerfile.ubuntu - platforms: linux/amd64,linux/arm64 - push: true - tags: | - "${{ env.CONTAINER_REGISTRY }}/${{ steps.build_variables.outputs.REPO }}:${{ steps.release_info.outputs.RELEASE_VERSION }}-unvalidated-ubuntu" - "${{ env.CONTAINER_REGISTRY }}/${{ steps.build_variables.outputs.REPO }}:${{ steps.release_info.outputs.RELEASE_VERSION }}-${{ steps.build_variables.outputs.VERSION }}-unvalidated-ubuntu" - - name: Create release - if: steps.release_info.outputs.SKIP_RELEASE == 'false' - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.ref }} - release_name: ${{ github.event.repository.name }} ${{ github.ref }} - body: | - ${{ steps.release_info.outputs.CHANGELOG }} - draft: false - prerelease: ${{ steps.release_info.outputs.IS_CANDIDATE }} diff --git a/Dockerfile.compile b/Dockerfile.compile index 4094234f44a..1b12714ff41 100644 --- a/Dockerfile.compile +++ b/Dockerfile.compile @@ -1,4 +1,6 @@ -FROM openjdk:11-slim +FROM alpine:3.18 +RUN apk add --update \ + openjdk17-jre-headless \ RUN apt-get update && apt-get install -y \ git diff --git a/Dockerfile.ubuntu b/Dockerfile.ubuntu index 9ed2c2c5aab..d4f16122192 100644 --- a/Dockerfile.ubuntu +++ b/Dockerfile.ubuntu @@ -1,4 +1,4 @@ -FROM ubuntu:bionic +FROM ubuntu:jammy LABEL maintainer="sig-platform@spinnaker.io" WORKDIR /opt/deck diff --git a/docker/ports.conf.gen b/docker/ports.conf.gen index c933beca13b..a9eee73e0b9 100644 --- a/docker/ports.conf.gen +++ b/docker/ports.conf.gen @@ -2,4 +2,7 @@ Listen {%DECK_HOST%}:{%DECK_PORT%} SSLPassPhraseDialog exec:/etc/apache2/passphrase - \ No newline at end of file + + +ServerSignature Off +ServerTokens Prod diff --git a/gradle.properties b/gradle.properties index 68ead1f18b5..46932294d9e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -spinnakerGradleVersion=8.25.0 +spinnakerGradleVersion=8.32.1 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 490fda8577d..943f0cbfa75 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a4b4429748d..508322917bd 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 2fe81a7d95e..65dcd68d65c 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,78 +17,113 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -97,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -105,79 +140,105 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 62bd9b9ccef..93e3f59f135 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +55,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,38 +65,26 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/package.json b/package.json index 189c3f8b7bf..7e441d25c02 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,8 @@ "registry": "http://artifacts.netflix.com/api/npm/npm-local" }, "engines": { - "node": ">=12.16.0", - "npm": ">=6.13.4", + "node": ">=14.21.3", + "npm": ">=6.14.18", "yarn": ">=1.21.1" }, "private": true, diff --git a/packages/amazon/CHANGELOG.md b/packages/amazon/CHANGELOG.md index f4ca39fdf2a..d2fb5d58a09 100644 --- a/packages/amazon/CHANGELOG.md +++ b/packages/amazon/CHANGELOG.md @@ -3,6 +3,51 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.14.3](https://github.com/spinnaker/deck/compare/@spinnaker/amazon@0.14.2...@spinnaker/amazon@0.14.3) (2023-12-07) + + +### Bug Fixes + +* **amazon:** Allow scaling bounds to use floats between input steps ([#10059](https://github.com/spinnaker/deck/issues/10059)) ([5c1ebfd](https://github.com/spinnaker/deck/commit/5c1ebfdf924e73aa6877943cb008c216177b8256)) +* **lambda:** available Runtimes shared between Deploy stage and Functions tab ([#10050](https://github.com/spinnaker/deck/issues/10050)) ([889d769](https://github.com/spinnaker/deck/commit/889d769c600e298917ec2471cd88a4bdd808ed91)) + + + + + +## [0.14.2](https://github.com/spinnaker/deck/compare/@spinnaker/amazon@0.14.1...@spinnaker/amazon@0.14.2) (2023-10-16) + + +### Bug Fixes + +* **publish:** set access config in deck libraries ([#10049](https://github.com/spinnaker/deck/issues/10049)) ([2a5ebe2](https://github.com/spinnaker/deck/commit/2a5ebe25662eeb9d41b5071749266bf9d6d51104)) + + + + + +## [0.14.1](https://github.com/spinnaker/deck/compare/@spinnaker/amazon@0.14.0...@spinnaker/amazon@0.14.1) (2023-09-06) + + +### Bug Fixes + +* Scaling bounds should parse float not int ([#10026](https://github.com/spinnaker/deck/issues/10026)) ([b763cae](https://github.com/spinnaker/deck/commit/b763cae826039df46b8dbe019689316ff5034e33)) + + + + + +# [0.14.0](https://github.com/spinnaker/deck/compare/@spinnaker/amazon@0.13.9...@spinnaker/amazon@0.14.0) (2023-07-20) + + +### Features + +* **lambda:** Migrate Lambda plugin to OSS ([#9988](https://github.com/spinnaker/deck/issues/9988)) ([11f1cab](https://github.com/spinnaker/deck/commit/11f1cabb8efe8d7e034faf06ae3cb455eef6369a)), closes [#9984](https://github.com/spinnaker/deck/issues/9984) + + + + + ## [0.13.9](https://github.com/spinnaker/deck/compare/@spinnaker/amazon@0.13.8...@spinnaker/amazon@0.13.9) (2023-06-02) **Note:** Version bump only for package @spinnaker/amazon diff --git a/packages/amazon/package.json b/packages/amazon/package.json index 7449b0f61e9..ff267f3aca8 100644 --- a/packages/amazon/package.json +++ b/packages/amazon/package.json @@ -1,9 +1,12 @@ { "name": "@spinnaker/amazon", "license": "Apache-2.0", - "version": "0.13.9", + "version": "0.14.3", "module": "dist/index.js", "typings": "dist/index.d.ts", + "publishConfig": { + "access": "public" + }, "scripts": { "clean": "shx rm -rf dist", "prepublishOnly": "npm run build", @@ -13,7 +16,7 @@ "lib": "npm run build" }, "dependencies": { - "@spinnaker/core": "^0.24.1", + "@spinnaker/core": "^0.28.0", "@uirouter/angularjs": "1.0.26", "@uirouter/core": "6.0.8", "@uirouter/react": "1.0.7", @@ -39,9 +42,9 @@ "rxjs": "6.6.7" }, "devDependencies": { - "@spinnaker/eslint-plugin": "^3.0.1", + "@spinnaker/eslint-plugin": "^3.0.2", "@spinnaker/mocks": "1.0.7", - "@spinnaker/scripts": "^0.3.0", + "@spinnaker/scripts": "^0.4.0", "@types/angular": "1.6.26", "@types/angular-ui-bootstrap": "0.13.41", "@types/classnames": "2.2.0", diff --git a/packages/amazon/src/aws.module.ts b/packages/amazon/src/aws.module.ts index a23f9b89274..d06b6dc7c21 100644 --- a/packages/amazon/src/aws.module.ts +++ b/packages/amazon/src/aws.module.ts @@ -6,9 +6,9 @@ import { AWSProviderSettings } from './aws.settings'; import { COMMON_MODULE } from './common/common.module'; import './deploymentStrategy/rollingPush.strategy'; import { AmazonFunctionDetails } from './function'; -import { CreateLambdaFunction } from './function/CreateLambdaFunction'; +import { CreateLambdaFunction } from './function'; +import { AwsFunctionTransformer } from './function'; import { AWS_FUNCTION_MODULE } from './function/function.module'; -import { AwsFunctionTransformer } from './function/function.transformer'; import './help/amazon.help'; import { AwsImageReader } from './image'; import { AMAZON_INSTANCE_AWSINSTANCETYPE_SERVICE } from './instance/awsInstanceType.service'; @@ -18,11 +18,11 @@ import { INSTANCE_DNS_COMPONENT } from './instance/details/instanceDns.component import { INSTANCE_SECURITY_GROUPS_COMPONENT } from './instance/details/instanceSecurityGroups.component'; import { INSTANCE_STATUS_COMPONENT } from './instance/details/instanceStatus.component'; import { INSTANCE_TAGS_COMPONENT } from './instance/details/instanceTags.component'; -import { AmazonLoadBalancerClusterContainer } from './loadBalancer/AmazonLoadBalancerClusterContainer'; -import { AmazonLoadBalancersTag } from './loadBalancer/AmazonLoadBalancersTag'; -import { AmazonLoadBalancerChoiceModal } from './loadBalancer/configure/AmazonLoadBalancerChoiceModal'; +import { AmazonLoadBalancerClusterContainer } from './loadBalancer'; +import { AmazonLoadBalancersTag } from './loadBalancer'; +import { AmazonLoadBalancerChoiceModal } from './loadBalancer'; +import { AwsLoadBalancerTransformer } from './loadBalancer'; import { AWS_LOAD_BALANCER_MODULE } from './loadBalancer/loadBalancer.module'; -import { AwsLoadBalancerTransformer } from './loadBalancer/loadBalancer.transformer'; import amazonLogo from './logo/amazon.logo.svg'; import { AMAZON_PIPELINE_STAGES_BAKE_AWSBAKESTAGE } from './pipeline/stages/bake/awsBakeStage'; import { AMAZON_PIPELINE_STAGES_CLONESERVERGROUP_AWSCLONESERVERGROUPSTAGE } from './pipeline/stages/cloneServerGroup/awsCloneServerGroupStage'; diff --git a/packages/amazon/src/aws.validators.ts b/packages/amazon/src/aws.validators.ts index b899f616862..f0abed132f1 100644 --- a/packages/amazon/src/aws.validators.ts +++ b/packages/amazon/src/aws.validators.ts @@ -1,7 +1,7 @@ import { isEmpty } from 'lodash'; export const iamRoleValidator = (value: string, label: string) => { - const isIAMRole = value.match(/^arn:aws:iam::\d{12}:role\/?\/[a-zA-Z_0-9+=,.@\-_/]+/); + const isIAMRole = value.match(/^arn:aws:iam::\d{12}:role\/?\/[a-zA-Z_0-9+=,.@\-/]+/); return isIAMRole ? undefined : `Invalid role. ${label} must match regular expression: arn:aws:iam::d{12}:role/?[a-zA-Z_0-9+=,.@-_/]+`; @@ -9,10 +9,9 @@ export const iamRoleValidator = (value: string, label: string) => { export const s3BucketNameValidator = (value: string, label: string) => { const s3BucketName = value.match(/^[0-9A-Za-z.-]*[^.]$/); - const err = s3BucketName + return s3BucketName ? undefined : `Invalid S3 Bucket name. ${label} must match regular expression: [0-9A-Za-z.-]*[^.]$`; - return err; }; export const awsArnValidator = (value: string, label: string) => { @@ -25,3 +24,8 @@ export const awsArnValidator = (value: string, label: string) => { export const awsTagsValidator = (value: string | { [key: string]: string }, label: string) => { return isEmpty(value) ? `At least one ${label} is required` : undefined; }; + +export const simpleStringValidator = (value: string, label: string) => { + const simpleString = value.match(/^[0-9A-Za-z]*$/); + return simpleString ? undefined : `Invalid String Value. ${label} must match regular expression: [0-9A-Za-z]`; +}; diff --git a/packages/amazon/src/function/configure/FunctionBasicInformation.tsx b/packages/amazon/src/function/configure/FunctionBasicInformation.tsx index 545eee601e1..fe965b026b8 100644 --- a/packages/amazon/src/function/configure/FunctionBasicInformation.tsx +++ b/packages/amazon/src/function/configure/FunctionBasicInformation.tsx @@ -19,22 +19,7 @@ import { s3BucketNameValidator } from '../../aws.validators'; import type { IAmazonFunction } from '../../domain'; import type { IAmazonFunctionUpsertCommand } from '../../index'; -const availableRuntimes = [ - 'nodejs10.x', - 'nodejs12.x', - 'java8', - 'java11', - 'python2.7', - 'python3.6', - 'python3.7', - 'python3.8', - 'dotnetcore2.1', - 'dotnetcore3.1', - 'go1.x', - 'ruby2.5', - 'ruby2.7', - 'provided', -]; +import { availableRuntimes } from '../../pipeline/stages/deployLambda/components/function.constants'; export interface IFunctionProps { app: Application; diff --git a/packages/amazon/src/pipeline/stages/deleteLambda/DeleteLambdaFunctionStageForm.tsx b/packages/amazon/src/pipeline/stages/deleteLambda/DeleteLambdaFunctionStageForm.tsx new file mode 100644 index 00000000000..2e3bfe43111 --- /dev/null +++ b/packages/amazon/src/pipeline/stages/deleteLambda/DeleteLambdaFunctionStageForm.tsx @@ -0,0 +1,143 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; + +import type { + IAccount, + IAccountDetails, + IFormikStageConfigInjectedProps, + IFormInputProps, + IFunction, + IRegion, +} from '@spinnaker/core'; +import { AccountService, FormikFormField, HelpField, NumberInput, ReactSelectInput, useData } from '@spinnaker/core'; + +import { DeleteVersionList, DeleteVersionPicker } from './constants'; +import type { IAmazonFunctionSourceData } from '../../../domain'; + +export function DeleteLambdaFunctionStageForm(props: IFormikStageConfigInjectedProps) { + const { values } = props.formik; + const { functions } = props.application; + + const { result: fetchAccountsResult, status: fetchAccountsStatus } = useData( + () => AccountService.listAccounts('aws'), + [], + [], + ); + + const onAccountChange = (fieldName: string, fieldValue: any): void => { + props.formik.setFieldValue('region', null); + props.formik.setFieldValue('functionName', null); + + props.formik.setFieldValue(fieldName, fieldValue); + }; + + const onRegionChange = (fieldName: string, fieldValue: any): void => { + props.formik.setFieldValue('functionName', null); + + props.formik.setFieldValue(fieldName, fieldValue); + }; + + const availableFunctions = + values.account && values.region + ? functions.data + .filter((f: IFunction) => f.account === values.account) + .filter((f: IFunction) => f.region === values.region) + .map((f: IFunction) => f.functionName) + : []; + + return ( +
+ ( + acc.name)} + /> + )} + /> + ( + acc.name === values.account) + .flatMap((acc: IAccountDetails) => acc.regions) + .map((reg: IRegion) => reg.name)} + /> + )} + /> + ( + + )} + /> + ( + ( + + )} + /> + )} + /> + {values.version === '$PROVIDED' ? ( + ( + f.account === values.account) + .filter((f: IAmazonFunctionSourceData) => f.region === values.region) + .filter((f: IAmazonFunctionSourceData) => f.functionName === values.functionName) + .flatMap((f: IAmazonFunctionSourceData) => + Object.values(f.revisions).sort(function (a: number, b: number) { + return b - a; + }), + ) + .filter((r: any) => r !== '$LATEST')} + /> + )} + /> + ) : null} + {values.version === '$MOVING' ? ( + } + label="Prior Versions to Retain" + input={(props) => } + /> + ) : null} +
+ ); +} diff --git a/packages/amazon/src/pipeline/stages/deleteLambda/LambdaDeleteStage.tsx b/packages/amazon/src/pipeline/stages/deleteLambda/LambdaDeleteStage.tsx new file mode 100644 index 00000000000..ffd2476074e --- /dev/null +++ b/packages/amazon/src/pipeline/stages/deleteLambda/LambdaDeleteStage.tsx @@ -0,0 +1,86 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; + +import type { + IExecutionDetailsSectionProps, + IFormikStageConfigInjectedProps, + IStage, + IStageConfigProps, + IStageTypeConfig, +} from '@spinnaker/core'; +import { + ExecutionDetailsSection, + ExecutionDetailsTasks, + FormikStageConfig, + FormValidator, + HelpContentsRegistry, + StageFailureMessage, +} from '@spinnaker/core'; + +import { DeleteLambdaFunctionStageForm } from './DeleteLambdaFunctionStageForm'; + +export function DeleteLambdaExecutionDetails(props: IExecutionDetailsSectionProps) { + const { stage, name, current } = props; + return ( + + +
+

+ {' '} + Status: {stage.outputs.deleteTask === 'done' ? 'COMPLETE' : stage.outputs.deleteTask}{' '} +

+

+ {' '} + Deleted Version: {' '} + {stage.outputs['deleteTask:deleteVersion'] ? stage.outputs['deleteTask:deleteVersion'] : 'N/A'}{' '} +

+
+
+ ); +} + +function DeleteLambdaConfig(props: IStageConfigProps) { + return ( +
+ } + /> +
+ ); +} + +export const initialize = () => { + HelpContentsRegistry.register('aws.lambdaDeploymentStage.lambda', 'Lambda Name'); +}; + +function validate(stageConfig: IStage) { + const validator = new FormValidator(stageConfig); + validator.field('account', 'Account Name').required(); + + validator.field('region', 'Region').required(); + + validator.field('functionName', 'Lambda Function Name').required(); + + validator.field('version', 'Lambda Function Version').required(); + + return validator.validateForm(); +} + +// eslint-disable-next-line +export namespace DeleteLambdaExecutionDetails { + export const title = 'Delete Lambda Stage'; +} + +export const lambdaDeleteStage: IStageTypeConfig = { + key: 'Aws.LambdaDeleteStage', + label: `AWS Lambda Delete`, + description: 'Delete an AWS Lambda Function', + component: DeleteLambdaConfig, // stage config + executionDetailsSections: [DeleteLambdaExecutionDetails, ExecutionDetailsTasks], + validateFn: validate, +}; diff --git a/packages/amazon/src/pipeline/stages/deleteLambda/constants/deleteVersion.constants.ts b/packages/amazon/src/pipeline/stages/deleteLambda/constants/deleteVersion.constants.ts new file mode 100644 index 00000000000..d172309f67c --- /dev/null +++ b/packages/amazon/src/pipeline/stages/deleteLambda/constants/deleteVersion.constants.ts @@ -0,0 +1,37 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export interface IDeleteVersionConstant { + description: string; + label: string; + value: string; +} + +export const DeleteVersionList: IDeleteVersionConstant[] = [ + { + label: 'Newest Function Version', + value: '$LATEST', + description: 'Delete the most recently deployed function version when this stage starts.', + }, + { + label: 'Previous Function Version', + value: '$PREVIOUS', + description: 'Delete the second-most recently deployed function version when this stage starts.', + }, + { + label: 'Older Than N', + value: '$MOVING', + description: 'Delete all version but the N most recent versions.', + }, + { + label: 'Provide Version Number', + value: '$PROVIDED', + description: 'Provide a specific version number to delete.', + }, + { + label: 'All Function Versions', + value: '$ALL', + description: + 'Delete all function versions and function infrastructure. This will completely delete the Lambda function.', + }, +]; diff --git a/packages/amazon/src/pipeline/stages/deleteLambda/constants/deleteVersion.picker.tsx b/packages/amazon/src/pipeline/stages/deleteLambda/constants/deleteVersion.picker.tsx new file mode 100644 index 00000000000..cd48464ed70 --- /dev/null +++ b/packages/amazon/src/pipeline/stages/deleteLambda/constants/deleteVersion.picker.tsx @@ -0,0 +1,47 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; + +import type { IFormikStageConfigInjectedProps } from '@spinnaker/core'; + +import type { IDeleteVersionConstant } from './deleteVersion.constants'; +import { DeleteVersionList } from './deleteVersion.constants'; + +export interface IVersionPickerProps { + config: IFormikStageConfigInjectedProps; + value: string; + showingDetails: boolean; +} + +export interface IVersionPickerState { + value: string; + label: string; + description: string; +} + +export class DeleteVersionPicker extends React.Component { + constructor(props: IVersionPickerProps) { + super(props); + + const { value } = this.props; + + const versionDetails = DeleteVersionList.filter((v: IDeleteVersionConstant) => v.value === value)[0]; + + this.state = { + label: versionDetails.label, + value: versionDetails.value, + description: versionDetails.description, + }; + } + + public render() { + return ( +
+ {this.state.label} +
+ {this.state.description} +
+ ); + } +} diff --git a/packages/amazon/src/pipeline/stages/deleteLambda/constants/index.ts b/packages/amazon/src/pipeline/stages/deleteLambda/constants/index.ts new file mode 100644 index 00000000000..d63b1fe90f7 --- /dev/null +++ b/packages/amazon/src/pipeline/stages/deleteLambda/constants/index.ts @@ -0,0 +1,5 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export * from './deleteVersion.constants'; +export * from './deleteVersion.picker'; diff --git a/packages/amazon/src/pipeline/stages/deleteLambda/index.ts b/packages/amazon/src/pipeline/stages/deleteLambda/index.ts new file mode 100644 index 00000000000..a2bcdd62906 --- /dev/null +++ b/packages/amazon/src/pipeline/stages/deleteLambda/index.ts @@ -0,0 +1,11 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Registry, SETTINGS } from '@spinnaker/core'; +import { lambdaDeleteStage } from './LambdaDeleteStage'; + +export * from './LambdaDeleteStage'; + +if (SETTINGS.feature.lambdaAdditionalStages) { + Registry.pipeline.registerStage(lambdaDeleteStage); +} diff --git a/packages/amazon/src/pipeline/stages/deployCloudFormation/CloudFormationChangeSetInfo.tsx b/packages/amazon/src/pipeline/stages/deployCloudFormation/CloudFormationChangeSetInfo.tsx index 5a849ad7969..a89f4b0e774 100644 --- a/packages/amazon/src/pipeline/stages/deployCloudFormation/CloudFormationChangeSetInfo.tsx +++ b/packages/amazon/src/pipeline/stages/deployCloudFormation/CloudFormationChangeSetInfo.tsx @@ -13,7 +13,7 @@ export interface ICloudFormationChangeSetInfoProps { export const CloudFormationChangeSetInfo = (props: ICloudFormationChangeSetInfoProps) => { const { stage, stageconfig } = props; const [changeSetName, setChangeSetName] = useState( - (stage as any).changeSetName ? (stage as any).changeSetName : "ChangeSet-${ execution['id']}", + (stage as any).changeSetName ? (stage as any).changeSetName : "ChangeSet-${execution['id']}", ); const [executeChangeSet, setExecuteChangeSet] = useState((stage as any).executeChangeSet); const [actionOnReplacement, setActionOnReplacement] = useState((stage as any).actionOnReplacement); diff --git a/packages/amazon/src/pipeline/stages/deployLambda/components/AwsLambdaFunctionStageForm.tsx b/packages/amazon/src/pipeline/stages/deployLambda/components/AwsLambdaFunctionStageForm.tsx new file mode 100644 index 00000000000..bbe837f855b --- /dev/null +++ b/packages/amazon/src/pipeline/stages/deployLambda/components/AwsLambdaFunctionStageForm.tsx @@ -0,0 +1,139 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import classNames from 'classnames'; +import React from 'react'; +import type { Option } from 'react-select'; + +import type { IFormikStageConfigInjectedProps, IFormInputProps } from '@spinnaker/core'; +import { + FormikFormField, + HelpField, + MapEditorInput, + NumberInput, + ReactSelectInput, + TetheredCreatable, + TextInput, +} from '@spinnaker/core'; +import { NumberConcurrencyInput } from '@spinnaker/core/dist/presentation/forms/inputs/NumberConcurrencyInput'; + +import { BasicSettingsForm, ExecutionRoleForm, LambdaAtEdgeForm, NetworkForm, TriggerEventsForm } from './index'; + +export function AwsLambdaFunctionStageForm(props: IFormikStageConfigInjectedProps) { + const { values, errors } = props.formik; + + const className = classNames({ + well: true, + 'alert-danger': !!errors.functionName, + 'alert-info': !errors.functionName, + }); + + const onLayerChange = (o: Option, field: any) => { + props.formik.setFieldValue( + field, + o.map((layer: any) => layer.value), + ); + }; + + return ( +
+
+ Your function will be named: + + {values.functionName ? values.functionName : props.application.name} + null} /> +
+

Basic Settings

+ +

Execution Role

+ +

Environment

+ {values.enableLambdaAtEdge !== true ? ( + <> + } + /> + } + input={(props) => } + /> + + ) : ( +
Environment variables not available with Lambda@Edge functions.
+ )} +

Tags

+ } + /> +

Settings

+ } /> + + } + input={(inputProps: IFormInputProps) => ( + { + onLayerChange(e, 'layers'); + }} + value={values.layers ? values.layers.map((layer: string) => ({ value: layer, label: layer })) : []} + /> + )} + /> + + } + input={(props) => } + /> + } + input={(props) => } + /> + } + input={(props) => } + /> + +

Network

+ {values.enableLambdaAtEdge !== true ? ( + + ) : ( +
VPC configuration not available with Lambda@Edge functions.
+ )} +

Event Triggers

+ +

Debugging and Error Handling

+ Dead Letter Config + } + input={(props) => } + /> + X-Ray Tracing + } + input={(props) => } + /> +
+ ); +} diff --git a/packages/amazon/src/pipeline/stages/deployLambda/components/BasicSettingsForm.tsx b/packages/amazon/src/pipeline/stages/deployLambda/components/BasicSettingsForm.tsx new file mode 100644 index 00000000000..54249f6a049 --- /dev/null +++ b/packages/amazon/src/pipeline/stages/deployLambda/components/BasicSettingsForm.tsx @@ -0,0 +1,176 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; + +import type { Option } from 'react-select'; + +import type { + IAccount, + IAccountDetails, + IFormikStageConfigInjectedProps, + IFormInputProps, + IRegion, +} from '@spinnaker/core'; +import { + AccountService, + CheckboxInput, + FormikFormField, + HelpField, + NameUtils, + ReactSelectInput, + TetheredCreatable, + TextInput, + useData, +} from '@spinnaker/core'; + +import { availableRuntimes, lambdaHelpFields } from './function.constants'; + +export function BasicSettingsForm(props: IFormikStageConfigInjectedProps) { + const { values } = props.formik; + + const setFunctionName = () => { + const ns = NameUtils.getClusterName(props.application.name, values.stackName, values.detailName); + const fn = values.functionUid; + + props.formik.setFieldValue('functionName', `${ns}-${fn}`); + }; + + const onAliasChange = (o: Option, field: any) => { + props.formik.setFieldValue( + field, + o.map((layer: any) => layer.value), + ); + }; + + const onRegionChange = (fieldValue: string) => { + props.formik.setFieldValue('enableLambdaAtEdge', false); + props.formik.setFieldValue('region', fieldValue); + }; + + const onFunctionUidChange = (fieldValue: string) => { + props.formik.setFieldValue('functionUid', fieldValue); + setFunctionName(); + }; + + const onStackNameChange = (fieldValue: string) => { + props.formik.setFieldValue('stackName', fieldValue); + setFunctionName(); + }; + + const onDetailChange = (fieldValue: string) => { + props.formik.setFieldValue('detailName', fieldValue); + setFunctionName(); + }; + + const { result: fetchAccountsResult, status: fetchAccountsStatus } = useData( + () => AccountService.listAccounts('aws'), + [], + [], + ); + + return ( +
+ ( + acc.name)} + /> + )} + /> + ( + acc.name === values.account) + .flatMap((acc: IAccountDetails) => acc.regions) + .map((reg: IRegion) => reg.name)} + /> + )} + /> + + } + input={(props) => } + /> + + } + onChange={onStackNameChange} + input={(props) => } + /> + } + onChange={onDetailChange} + input={(props) => } + /> + + } + input={(inputProps: IFormInputProps) => ( + { + onAliasChange(e, 'aliases'); + }} + value={values.aliases ? values.aliases.map((alias: string) => ({ value: alias, label: alias })) : []} + /> + )} + /> + } + input={(props) => } + /> + } + input={(props) => } + /> + } + input={(props) => } + /> + } + input={(props) => } + /> + } + input={(props) => } + /> +
+ ); +} diff --git a/packages/amazon/src/pipeline/stages/deployLambda/components/EnvironmentForm.tsx b/packages/amazon/src/pipeline/stages/deployLambda/components/EnvironmentForm.tsx new file mode 100644 index 00000000000..b78fa583dec --- /dev/null +++ b/packages/amazon/src/pipeline/stages/deployLambda/components/EnvironmentForm.tsx @@ -0,0 +1,24 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; + +import { FormikFormField, HelpField, MapEditorInput, TextInput } from '@spinnaker/core'; + +export function ExecutionRoleForm() { + return ( +
+ } + /> + } + input={(props) => } + /> +
+ ); +} diff --git a/packages/amazon/src/pipeline/stages/deployLambda/components/ExecutionRoleForm.tsx b/packages/amazon/src/pipeline/stages/deployLambda/components/ExecutionRoleForm.tsx new file mode 100644 index 00000000000..7e12b3ab0d0 --- /dev/null +++ b/packages/amazon/src/pipeline/stages/deployLambda/components/ExecutionRoleForm.tsx @@ -0,0 +1,15 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import React from 'react'; + +import { FormikFormField, TextInput } from '@spinnaker/core'; + +export function ExecutionRoleForm() { + return ( + } + /> + ); +} diff --git a/packages/amazon/src/pipeline/stages/deployLambda/components/LambdaAtEdgeForm.tsx b/packages/amazon/src/pipeline/stages/deployLambda/components/LambdaAtEdgeForm.tsx new file mode 100644 index 00000000000..f3b27c56766 --- /dev/null +++ b/packages/amazon/src/pipeline/stages/deployLambda/components/LambdaAtEdgeForm.tsx @@ -0,0 +1,28 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; + +import type { IFormikStageConfigInjectedProps } from '@spinnaker/core'; +import { CheckboxInput, FormikFormField, HelpField } from '@spinnaker/core'; + +const helpFieldContent = { + lambdaAtEdge: + 'Validate AWS Lambda function configuration against Lambda@Edge requirements. This will not enable Lambda@Edge on this function. ', +}; +export function LambdaAtEdgeForm(props: IFormikStageConfigInjectedProps) { + const { values } = props.formik; + if (values.region !== 'us-east-1') { + return
Lambda@Edge is only available in region us-east-1.
; + } + return ( +
+ } + input={(props) => } + /> +
+ ); +} diff --git a/packages/amazon/src/pipeline/stages/deployLambda/components/NetworkForm.tsx b/packages/amazon/src/pipeline/stages/deployLambda/components/NetworkForm.tsx new file mode 100644 index 00000000000..f034c565737 --- /dev/null +++ b/packages/amazon/src/pipeline/stages/deployLambda/components/NetworkForm.tsx @@ -0,0 +1,123 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { uniqBy } from 'lodash'; +import React from 'react'; +import type { Option } from 'react-select'; + +import type { IFormikStageConfigInjectedProps, IFormInputProps, ISecurityGroup, ISubnet, IVpc } from '@spinnaker/core'; +import { + FormikFormField, + NetworkReader, + ReactInjector, + ReactSelectInput, + SubnetReader, + TetheredSelect, + useData, +} from '@spinnaker/core'; + +const toSubnetOption = (value: ISubnet): Option => { + return { value: value.id, label: value.id }; +}; + +export function NetworkForm(props: IFormikStageConfigInjectedProps) { + const { values } = props.formik; + + const onChangeVpc = (vpcs: any) => { + props.formik.setFieldValue('securityGroupIds', null); + props.formik.setFieldValue('subnetIds', null); + props.formik.setFieldValue('vpcId', vpcs.target.value); + }; + + const onChangeSubnet = (subnets: any) => { + const subnetsSelected = subnets.map((o: any) => o.value); + props.formik.setFieldValue('subnetIds', subnetsSelected); + }; + + const onChangeSG = (sgs: any) => { + const sgsSelected = sgs.map((o: any) => o.value); + props.formik.setFieldValue('securityGroupIds', sgsSelected); + }; + + const { result: fetchVpcsResult, status: fetchVpcsStatus } = useData( + () => NetworkReader.listNetworksByProvider('aws'), + [], + [], + ); + + const { result: fetchSubnetsResult } = useData(() => SubnetReader.listSubnetsByProvider('aws'), [], []); + + const { result: fetchSGsResult } = useData( + () => ReactInjector.securityGroupReader.getAllSecurityGroups(), + undefined, + [], + ); + + const availableVpcs = + values.account && values.region && fetchVpcsStatus !== 'PENDING' + ? fetchVpcsResult + .filter((v: IVpc) => v.deprecated === false) + .filter((v: IVpc) => v.account === values.account) + .filter((v: IVpc) => v.region === values.region) + .map((v: IVpc) => v.id) + : []; + + const dedupedSubnets = uniqBy( + fetchSubnetsResult.filter((s: ISubnet) => s.vpcId === values.vpcId), + 'id', + ); + + const availableSGs = + values.account && values.region && values.vpcId + ? fetchSGsResult[values.account]['aws'][values.region] + .filter((sg: ISecurityGroup) => sg.vpcId === values.vpcId) + .map((sg: ISecurityGroup) => ({ value: sg.id, label: sg.name })) + : []; + + return ( +
+ ( + + )} + /> +
+
+ Subnets +
+ {dedupedSubnets.length === 0 ? ( +
No subnets found in the selected account/region/VPC
+ ) : ( +
+ toSubnetOption(s))} + value={values.subnetIds} + onChange={onChangeSubnet} + /> +
+ )} +
+
+
+ Security Groups +
+ {availableSGs.length === 0 ? ( +
No security groups found in the selected account/region/VPC
+ ) : ( +
+ +
+ )} +
+
+ ); +} diff --git a/packages/amazon/src/pipeline/stages/deployLambda/components/TriggerEventsForm.tsx b/packages/amazon/src/pipeline/stages/deployLambda/components/TriggerEventsForm.tsx new file mode 100644 index 00000000000..440bcd684e8 --- /dev/null +++ b/packages/amazon/src/pipeline/stages/deployLambda/components/TriggerEventsForm.tsx @@ -0,0 +1,49 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; + +import type { Option } from 'react-select'; + +import type { IFormikStageConfigInjectedProps, IFormInputProps } from '@spinnaker/core'; +import { FormikFormField, HelpField, NumberInput, TetheredCreatable } from '@spinnaker/core'; + +export function TriggerEventsForm(props: IFormikStageConfigInjectedProps) { + const { values } = props.formik; + + const onChange = (o: Option, field: any) => { + props.formik.setFieldValue( + field, + o.map((arn: any) => arn.value), + ); + }; + + return ( +
+ + } + input={(inputProps: IFormInputProps) => ( + { + onChange(e, 'triggerArns'); + }} + value={values.triggerArns ? values.triggerArns.map((arn: string) => ({ value: arn, label: arn })) : []} + /> + )} + /> + + } + /> +
+ ); +} diff --git a/packages/amazon/src/pipeline/stages/deployLambda/components/function.constants.ts b/packages/amazon/src/pipeline/stages/deployLambda/components/function.constants.ts new file mode 100644 index 00000000000..5b517b8e91c --- /dev/null +++ b/packages/amazon/src/pipeline/stages/deployLambda/components/function.constants.ts @@ -0,0 +1,32 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html +export const availableRuntimes = [ + 'nodejs12.x', + 'nodejs14.x', + 'nodejs16.x', + 'nodejs18.x', + 'java8', + 'java8.al2', + 'java11', + 'java17', + 'python3.7', + 'python3.8', + 'python3.9', + 'python3.10', + 'dotnetcore3.1', + 'dotnet7', + 'dotnet6', + 'dotnet5.0', + 'go1.x', + 'ruby2.7', + 'provided', + 'provided.al2', +]; + +export const lambdaHelpFields = { + stack: + '(Optional) Stack is naming components of a function, used to create vertical stacks of dependent services for integration testing.', + detail: + '(Optional) Detail is a string of free-form alphanumeric characters to describe any other variables in naming a function.', +}; diff --git a/packages/amazon/src/pipeline/stages/deployLambda/components/index.ts b/packages/amazon/src/pipeline/stages/deployLambda/components/index.ts new file mode 100644 index 00000000000..72391e4eabe --- /dev/null +++ b/packages/amazon/src/pipeline/stages/deployLambda/components/index.ts @@ -0,0 +1,8 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export * from './BasicSettingsForm'; +export * from './NetworkForm'; +export * from './ExecutionRoleForm'; +export * from './TriggerEventsForm'; +export * from './LambdaAtEdgeForm'; diff --git a/packages/amazon/src/pipeline/stages/deployLambda/config/LambdaDeploymentStage.tsx b/packages/amazon/src/pipeline/stages/deployLambda/config/LambdaDeploymentStage.tsx new file mode 100644 index 00000000000..a99f031cb62 --- /dev/null +++ b/packages/amazon/src/pipeline/stages/deployLambda/config/LambdaDeploymentStage.tsx @@ -0,0 +1,20 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import type { IStageTypeConfig } from '@spinnaker/core'; +import { ExecutionDetailsTasks, HelpContentsRegistry } from '@spinnaker/core'; + +import { LambdaDeploymentConfig, validate } from './LambdaDeploymentStageConfig'; +import { LambdaDeploymentExecutionDetails } from './LambdaDeploymentStageExecutionDetails'; + +export const initialize = () => { + HelpContentsRegistry.register('aws.lambdaDeploymentStage.lambda', 'Lambda Name'); +}; + +export const lambdaDeploymentStage: IStageTypeConfig = { + key: 'Aws.LambdaDeploymentStage', + label: `AWS Lambda Deployment`, + description: 'Create a Single AWS Lambda Function', + component: LambdaDeploymentConfig, // stage config + executionDetailsSections: [LambdaDeploymentExecutionDetails, ExecutionDetailsTasks], + validateFn: validate, +}; diff --git a/packages/amazon/src/pipeline/stages/deployLambda/config/LambdaDeploymentStageConfig.tsx b/packages/amazon/src/pipeline/stages/deployLambda/config/LambdaDeploymentStageConfig.tsx new file mode 100644 index 00000000000..5cedb70e6c2 --- /dev/null +++ b/packages/amazon/src/pipeline/stages/deployLambda/config/LambdaDeploymentStageConfig.tsx @@ -0,0 +1,64 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import React from 'react'; + +import type { IFormikStageConfigInjectedProps, IStage, IStageConfigProps } from '@spinnaker/core'; +import { FormikStageConfig, FormValidator } from '@spinnaker/core'; + +import { upsertDefaults } from './UpsertDefaults'; +import { + awsArnValidator, + iamRoleValidator, + s3BucketNameValidator, + simpleStringValidator, +} from '../../../../aws.validators'; +import { AwsLambdaFunctionStageForm } from '../components/AwsLambdaFunctionStageForm'; +import { constructNewAwsFunctionTemplate } from './function.defaults'; + +export function LambdaDeploymentConfig(props: IStageConfigProps) { + const defaultFunction = constructNewAwsFunctionTemplate(); + + return ( +
+ } + /> +
+ ); +} + +export function validate(stageConfig: IStage) { + const validator = new FormValidator(stageConfig); + + validator.field('runtime', 'Runtime').required(); + validator.field('s3key', 'S3 Object Key').required(); + validator.field('handler', 'Handler').required(); + validator.field('functionUid', 'Function Name').required(); + + validator.field('stackName', 'Stack Name').optional().withValidators(simpleStringValidator); + + validator.field('detailName', 'Detail Name').optional().withValidators(simpleStringValidator); + + validator.field('s3bucket', 'S3 Bucket Name').required().withValidators(s3BucketNameValidator); + + validator.field('role', 'Role ARN').required().withValidators(iamRoleValidator); + + validator + .field('triggerArns', 'Trigger ARNs') + .optional() + .withValidators((value: any, _: string) => { + const tmp: any[] = value.map((arn: string) => { + return awsArnValidator(arn, arn); + }); + const ret: boolean = tmp.every((el) => el === undefined); + return ret + ? undefined + : 'Invalid ARN. Event ARN must match regular expression: /^arn:aws[a-zA-Z-]?:[a-zA-Z_0-9.-]+:./'; + }); + + return validator.validateForm(); +} diff --git a/packages/amazon/src/pipeline/stages/deployLambda/config/LambdaDeploymentStageExecutionDetails.tsx b/packages/amazon/src/pipeline/stages/deployLambda/config/LambdaDeploymentStageExecutionDetails.tsx new file mode 100644 index 00000000000..72b7be6acac --- /dev/null +++ b/packages/amazon/src/pipeline/stages/deployLambda/config/LambdaDeploymentStageExecutionDetails.tsx @@ -0,0 +1,31 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; + +import type { IExecutionDetailsSectionProps } from '@spinnaker/core'; +import { ExecutionDetailsSection, StageFailureMessage } from '@spinnaker/core'; + +export function LambdaDeploymentExecutionDetails(props: IExecutionDetailsSectionProps) { + const { stage, current, name } = props; + return ( + + +
+

+ {' '} + Function Name: {stage.outputs.functionName ? stage.outputs.functionName : 'N/A'}{' '} +

+

+ {' '} + Function ARN: {stage.outputs.functionARN ? stage.outputs.functionARN : 'N/A'}{' '} +

+
+
+ ); +} + +// eslint-disable-next-line +export namespace LambdaDeploymentExecutionDetails { + export const title = 'Lambda Deployment Stage'; +} diff --git a/packages/amazon/src/pipeline/stages/deployLambda/config/UpsertDefaults.tsx b/packages/amazon/src/pipeline/stages/deployLambda/config/UpsertDefaults.tsx new file mode 100644 index 00000000000..793077b5acb --- /dev/null +++ b/packages/amazon/src/pipeline/stages/deployLambda/config/UpsertDefaults.tsx @@ -0,0 +1,21 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { isNil, isString } from 'lodash'; + +function isEmptyString(val: any) { + if (isString(val)) { + if (isNil(val) || val === '') { + return true; + } + } + return false; +} + +export function upsertDefaults(initialValues: any, defaultValues: any) { + Object.entries(defaultValues).forEach(([key, value]) => { + if (!initialValues[key] && !isEmptyString(value)) { + initialValues[key] = value; + } + }); + return initialValues; +} diff --git a/packages/amazon/src/pipeline/stages/deployLambda/config/function.defaults.ts b/packages/amazon/src/pipeline/stages/deployLambda/config/function.defaults.ts new file mode 100644 index 00000000000..f18eab6c3ce --- /dev/null +++ b/packages/amazon/src/pipeline/stages/deployLambda/config/function.defaults.ts @@ -0,0 +1,39 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import type { IAmazonFunctionUpsertCommand } from '../../../../domain'; + +export function constructNewAwsFunctionTemplate(): IAmazonFunctionUpsertCommand { + const defaultCredentials = ''; + const defaultRegion = ''; + return { + role: '', + runtime: '', + s3key: '', + s3bucket: '', + handler: '', + functionName: '', + publish: false, + tags: {}, + memorySize: 128, + description: '', + + credentials: defaultCredentials, + cloudProvider: 'aws', + region: defaultRegion, + envVariables: {}, + + tracingConfig: { + mode: 'PassThrough', + }, + kmskeyArn: '', + vpcId: '', + subnetIds: [], + securityGroupIds: [], + timeout: 3, + deadLetterConfig: { + targetArn: '', + }, + operation: '', + targetGroups: '', + }; +} diff --git a/packages/amazon/src/pipeline/stages/deployLambda/index.ts b/packages/amazon/src/pipeline/stages/deployLambda/index.ts new file mode 100644 index 00000000000..bd78a1dd344 --- /dev/null +++ b/packages/amazon/src/pipeline/stages/deployLambda/index.ts @@ -0,0 +1,11 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Registry, SETTINGS } from '@spinnaker/core'; +import { lambdaDeploymentStage } from './config/LambdaDeploymentStage'; + +export * from './config/LambdaDeploymentStage'; + +if (SETTINGS.feature.lambdaAdditionalStages) { + Registry.pipeline.registerStage(lambdaDeploymentStage); +} diff --git a/packages/amazon/src/pipeline/stages/invokeLambda/InvokeLambdaFunctionStageForm.tsx b/packages/amazon/src/pipeline/stages/invokeLambda/InvokeLambdaFunctionStageForm.tsx new file mode 100644 index 00000000000..327ed12e26f --- /dev/null +++ b/packages/amazon/src/pipeline/stages/invokeLambda/InvokeLambdaFunctionStageForm.tsx @@ -0,0 +1,106 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import React from 'react'; + +import type { + IAccount, + IAccountDetails, + IFormikStageConfigInjectedProps, + IFormInputProps, + IFunction, + IRegion, +} from '@spinnaker/core'; +import { AccountService, FormikFormField, ReactSelectInput, TextInput, useData } from '@spinnaker/core'; + +import { InvokeLambdaOperation } from './components'; + +export function InvokeLambdaFunctionStageForm(props: IFormikStageConfigInjectedProps) { + const { values } = props.formik; + const { functions } = props.application; + + const { result: fetchAccountsResult, status: fetchAccountsStatus } = useData( + () => AccountService.listAccounts('aws'), + [], + [], + ); + + const onAccountChange = (fieldValue: any): void => { + props.formik.setFieldValue('region', null); + props.formik.setFieldValue('functionName', null); + + props.formik.setFieldValue('account', fieldValue); + }; + + const onRegionChange = (fieldValue: any): void => { + props.formik.setFieldValue('functionName', null); + props.formik.setFieldValue('region', fieldValue); + }; + + const availableFunctions = + values.account && values.region + ? functions.data + .filter((f: IFunction) => f.account === values.account) + .filter((f: IFunction) => f.region === values.region) + .map((f: IFunction) => f.functionName) + : []; + + return ( +
+

Basic Settings

+ ( + acc.name)} + /> + )} + /> + ( + acc.name === values.account) + .flatMap((acc: IAccountDetails) => acc.regions) + .map((reg: IRegion) => reg.name)} + /> + )} + /> + ( + + )} + /> + + } + /> + +

Invoke Settings

+ +
+ ); +} diff --git a/packages/amazon/src/pipeline/stages/invokeLambda/LambdaInvokeStage.tsx b/packages/amazon/src/pipeline/stages/invokeLambda/LambdaInvokeStage.tsx new file mode 100644 index 00000000000..45000fe3e48 --- /dev/null +++ b/packages/amazon/src/pipeline/stages/invokeLambda/LambdaInvokeStage.tsx @@ -0,0 +1,125 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; + +import type { + IExecutionDetailsSectionProps, + IFormikStageConfigInjectedProps, + IStage, + IStageConfigProps, + IStageTypeConfig, +} from '@spinnaker/core'; +import { + ExecutionDetailsSection, + ExecutionDetailsTasks, + FormikStageConfig, + FormValidator, + HelpContentsRegistry, + StageFailureMessage, +} from '@spinnaker/core'; + +import { InvokeLambdaFunctionStageForm } from './InvokeLambdaFunctionStageForm'; +import { awsArnValidator } from '../../../aws.validators'; + +export function InvokeLambdaExecutionDetails(props: IExecutionDetailsSectionProps) { + const { stage } = props; + + return ( + + +
+

Function Name: {stage.outputs.functionName ? stage.outputs.functionName : 'N/A'}

+

+ {' '} + Deployed Alias:{' '} + {stage.outputs['deployment:aliasDeployed'] ? stage.outputs['deployment:aliasDeployed'] : 'N/A'}{' '} +

+

+ {' '} + Deployed Major Version:{' '} + {stage.outputs['deployment:majorVersionDeployed'] + ? stage.outputs['deployment:majorVersionDeployed'] + : 'N/A'}{' '} +

+
+
+ ); +} + +/* + IStageConfigProps defines properties passed to all Spinnaker Stages. + See IStageConfigProps.ts (https://github.com/spinnaker/deck/blob/master/app/scripts/modules/core/src/pipeline/config/stages/common/IStageConfigProps.ts) for a complete list of properties. + Pass a JSON object to the `updateStageField` method to add the `account` to the Stage. + + This method returns JSX (https://reactjs.org/docs/introducing-jsx.html) that gets displayed in the Spinnaker UI. + */ +function InvokeLambdaConfig(props: IStageConfigProps) { + return ( +
+ } + /> +
+ ); +} + +/* + This is a contrived example of how to use an `initialize` function to hook into arbitrary Deck services. + This `initialize` function provides the help field text for the `LambdaDeploymentConfig` stage form defined above. + + You can hook into any service exported by the `@spinnaker/core` NPM module, e.g.: + - CloudProviderRegistry + - DeploymentStrategyRegistry + + When you use a registry, you are diving into Deck's implementation to add functionality. + These registries and their methods may change without warning. +*/ +export const initialize = () => { + HelpContentsRegistry.register('aws.lambdaDeploymentStage.lambda', 'Lambda Name'); +}; + +function validate(stageConfig: IStage) { + const validator = new FormValidator(stageConfig); + + validator + .field('triggerArns', 'Trigger ARNs') + .optional() + .withValidators((value: any, _: string) => { + const tmp: any[] = value.map((arn: string) => { + return awsArnValidator(arn, arn); + }); + const ret: boolean = tmp.every((el) => el === undefined); + return ret + ? undefined + : 'Invalid ARN. Event ARN must match regular expression: /^arn:aws[a-zA-Z-]?:[a-zA-Z_0-9.-]+:./'; + }); + + return validator.validateForm(); +} + +// eslint-disable-next-line +export namespace InvokeLambdaExecutionDetails { + export const title = 'Invoke Lambda Stage'; +} + +/* + Define Spinnaker Stages with IStageTypeConfig. + Required options: https://github.com/spinnaker/deck/master/app/scripts/modules/core/src/domain/IStageTypeConfig.ts + - label -> The name of the Stage + - description -> Long form that describes what the Stage actually does + - key -> A unique name for the Stage in the UI; ties to Orca backend + - component -> The rendered React component + - validateFn -> A validation function for the stage config form. + */ +export const lambdaInvokeStage: IStageTypeConfig = { + key: 'Aws.LambdaInvokeStage', + label: `AWS Lambda Invoke`, + description: 'Invoke a Lambda function', + component: InvokeLambdaConfig, // stage config + executionDetailsSections: [InvokeLambdaExecutionDetails, ExecutionDetailsTasks], + validateFn: validate, +}; diff --git a/packages/amazon/src/pipeline/stages/invokeLambda/components/InvokeLambdaOperation.tsx b/packages/amazon/src/pipeline/stages/invokeLambda/components/InvokeLambdaOperation.tsx new file mode 100644 index 00000000000..d328d53735f --- /dev/null +++ b/packages/amazon/src/pipeline/stages/invokeLambda/components/InvokeLambdaOperation.tsx @@ -0,0 +1,77 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; + +import type { IArtifact, IExpectedArtifact, IFormikStageConfigInjectedProps } from '@spinnaker/core'; +import { + ArtifactTypePatterns, + excludeAllTypesExcept, + FormikFormField, + NumberInput, + StageArtifactSelectorDelegate, +} from '@spinnaker/core'; + +export function InvokeLambdaOperation(props: IFormikStageConfigInjectedProps) { + const { values } = props.formik; + + const excludedArtifactTypes = excludeAllTypesExcept( + ArtifactTypePatterns.BITBUCKET_FILE, + ArtifactTypePatterns.CUSTOM_OBJECT, + ArtifactTypePatterns.EMBEDDED_BASE64, + ArtifactTypePatterns.GCS_OBJECT, + ArtifactTypePatterns.GITHUB_FILE, + ArtifactTypePatterns.GITLAB_FILE, + ArtifactTypePatterns.S3_OBJECT, + ArtifactTypePatterns.HTTP_FILE, + ); + + const onTemplateArtifactEdited = (artifact: IArtifact, name: string) => { + props.formik.setFieldValue(`${name}.id`, null); + props.formik.setFieldValue(`${name}.artifact`, artifact); + props.formik.setFieldValue(`${name}.account`, artifact.artifactAccount); + }; + + const onTemplateArtifactSelected = (id: string, name: string) => { + props.formik.setFieldValue(`${name}.id`, id); + props.formik.setFieldValue(`${name}.artifact`, null); + }; + + const getInputArtifact = (stage: any, name: string) => { + if (!stage[name]) { + return { + account: '', + id: '', + }; + } else { + return stage[name]; + } + }; + + return ( +
+ } /> + } + /> + + { + onTemplateArtifactEdited(artifact, 'payloadArtifact'); + }} + helpKey={''} + onExpectedArtifactSelected={(artifact: IExpectedArtifact) => + onTemplateArtifactSelected(artifact.id, 'payloadrtifact') + } + pipeline={props.pipeline} + stage={values} + /> +
+ ); +} diff --git a/packages/amazon/src/pipeline/stages/invokeLambda/components/index.ts b/packages/amazon/src/pipeline/stages/invokeLambda/components/index.ts new file mode 100644 index 00000000000..7843372e5ff --- /dev/null +++ b/packages/amazon/src/pipeline/stages/invokeLambda/components/index.ts @@ -0,0 +1,4 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export * from './InvokeLambdaOperation'; diff --git a/packages/amazon/src/pipeline/stages/invokeLambda/index.ts b/packages/amazon/src/pipeline/stages/invokeLambda/index.ts new file mode 100644 index 00000000000..5ca923ad4ea --- /dev/null +++ b/packages/amazon/src/pipeline/stages/invokeLambda/index.ts @@ -0,0 +1,11 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Registry, SETTINGS } from '@spinnaker/core'; +import { lambdaInvokeStage } from './LambdaInvokeStage'; + +export * from './LambdaInvokeStage'; + +if (SETTINGS.feature.lambdaAdditionalStages) { + Registry.pipeline.registerStage(lambdaInvokeStage); +} diff --git a/packages/amazon/src/pipeline/stages/routeLambda/LambdaRouteStage.tsx b/packages/amazon/src/pipeline/stages/routeLambda/LambdaRouteStage.tsx new file mode 100644 index 00000000000..ba5ad5e0cc3 --- /dev/null +++ b/packages/amazon/src/pipeline/stages/routeLambda/LambdaRouteStage.tsx @@ -0,0 +1,98 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; + +import type { + IExecutionDetailsSectionProps, + IFormikStageConfigInjectedProps, + IStage, + IStageConfigProps, + IStageTypeConfig, +} from '@spinnaker/core'; +import { + ExecutionDetailsSection, + ExecutionDetailsTasks, + FormikStageConfig, + FormValidator, + HelpContentsRegistry, + StageFailureMessage, +} from '@spinnaker/core'; + +import { RouteLambdaFunctionStageForm } from './RouteLambdaFunctionStageForm'; +import { awsArnValidator } from '../../../aws.validators'; + +export function RouteLambdaExecutionDetails(props: IExecutionDetailsSectionProps) { + const { stage } = props; + + return ( + + +
+

Function Name: {stage.outputs.functionName ? stage.outputs.functionName : 'N/A'}

+

+ {' '} + Deployed Alias:{' '} + {stage.outputs['deployment:aliasDeployed'] ? stage.outputs['deployment:aliasDeployed'] : 'N/A'}{' '} +

+

+ {' '} + Deployed Major Version:{' '} + {stage.outputs['deployment:majorVersionDeployed'] + ? stage.outputs['deployment:majorVersionDeployed'] + : 'N/A'}{' '} +

+
+
+ ); +} + +function RouteLambdaConfig(props: IStageConfigProps) { + return ( +
+ } + /> +
+ ); +} + +export const initialize = () => { + HelpContentsRegistry.register('aws.lambdaDeploymentStage.lambda', 'Lambda Name'); +}; + +function validate(stageConfig: IStage) { + const validator = new FormValidator(stageConfig); + + validator + .field('triggerArns', 'Trigger ARNs') + .optional() + .withValidators((value: any, _: string) => { + const tmp: any[] = value.map((arn: string) => { + return awsArnValidator(arn, arn); + }); + const ret: boolean = tmp.every((el) => el === undefined); + return ret + ? undefined + : 'Invalid ARN. Event ARN must match regular expression: /^arn:aws[a-zA-Z-]?:[a-zA-Z_0-9.-]+:./'; + }); + + return validator.validateForm(); +} + +// eslint-disable-next-line +export namespace RouteLambdaExecutionDetails { + export const title = 'Route Lambda Traffic Stage'; +} + +export const lambdaRouteStage: IStageTypeConfig = { + key: 'Aws.LambdaTrafficRoutingStage', + label: `AWS Lambda Route`, + description: 'Route traffic across various versions of your Lambda function', + component: RouteLambdaConfig, // stage config + executionDetailsSections: [RouteLambdaExecutionDetails, ExecutionDetailsTasks], + validateFn: validate, +}; diff --git a/packages/amazon/src/pipeline/stages/routeLambda/RouteLambdaFunctionStageForm.tsx b/packages/amazon/src/pipeline/stages/routeLambda/RouteLambdaFunctionStageForm.tsx new file mode 100644 index 00000000000..17f25e76319 --- /dev/null +++ b/packages/amazon/src/pipeline/stages/routeLambda/RouteLambdaFunctionStageForm.tsx @@ -0,0 +1,151 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import React from 'react'; + +import type { + IAccount, + IAccountDetails, + IFormikStageConfigInjectedProps, + IFormInputProps, + IFunction, + IRegion, +} from '@spinnaker/core'; +import { + AccountService, + FormikFormField, + HelpField, + NumberInput, + ReactSelectInput, + TextInput, + useData, +} from '@spinnaker/core'; +import { NumberConcurrencyInput } from '@spinnaker/core/dist/presentation/forms/inputs/NumberConcurrencyInput'; + +import { TriggerEventsForm } from './TriggerEventsForm'; +import { DeploymentStrategyForm } from './components'; +import { DeploymentStrategyList, DeploymentStrategyPicker } from './constants'; + +export function RouteLambdaFunctionStageForm(props: IFormikStageConfigInjectedProps) { + const { values } = props.formik; + const { functions } = props.application; + + const { result: fetchAccountsResult, status: fetchAccountsStatus } = useData( + () => AccountService.listAccounts('aws'), + [], + [], + ); + + const onAccountChange = (fieldValue: any): void => { + props.formik.setFieldValue('region', null); + props.formik.setFieldValue('functionName', null); + + props.formik.setFieldValue('account', fieldValue); + }; + + const onRegionChange = (fieldValue: any): void => { + props.formik.setFieldValue('functionName', null); + props.formik.setFieldValue('region', fieldValue); + }; + + const availableFunctions = + values.account && values.region + ? functions.data + .filter((f: IFunction) => f.account === values.account) + .filter((f: IFunction) => f.region === values.region) + .map((f: IFunction) => f.functionName) + : []; + + return ( +
+

Basic Settings

+ ( + acc.name)} + /> + )} + /> + ( + acc.name === values.account) + .flatMap((acc: IAccountDetails) => acc.regions) + .map((reg: IRegion) => reg.name)} + /> + )} + /> + ( + + )} + /> + + } + /> + +

Alias Settings

+ + + + } + input={(props) => + values.deploymentStrategy === '$WEIGHTED' ? ( + + ) : ( + + ) + } + required={false} + /> + +

Deployment Strategy

+ } + input={(inputProps: IFormInputProps) => ( + ( + + )} + /> + )} + /> + {values.deploymentStrategy ? : null} +
+ ); +} diff --git a/packages/amazon/src/pipeline/stages/routeLambda/TriggerEventsForm.tsx b/packages/amazon/src/pipeline/stages/routeLambda/TriggerEventsForm.tsx new file mode 100644 index 00000000000..8906f0f1f7b --- /dev/null +++ b/packages/amazon/src/pipeline/stages/routeLambda/TriggerEventsForm.tsx @@ -0,0 +1,51 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; + +import type { Option } from 'react-select'; + +import type { IFormikStageConfigInjectedProps, IFormInputProps } from '@spinnaker/core'; +import { FormikFormField, HelpField, NumberInput, TetheredCreatable } from '@spinnaker/core'; + +export function TriggerEventsForm(props: IFormikStageConfigInjectedProps) { + const { values } = props.formik; + + const onChange = (o: Option, field: any) => { + props.formik.setFieldValue( + field, + o.map((arn: any) => arn.value), + ); + }; + + return ( +
+ + } + input={(inputProps: IFormInputProps) => ( + { + onChange(e, 'triggerArns'); + }} + value={values.triggerArns ? values.triggerArns.map((arn: string) => ({ value: arn, label: arn })) : []} + /> + )} + required={false} + /> + + } + required={false} + /> +
+ ); +} diff --git a/packages/amazon/src/pipeline/stages/routeLambda/components/BlueGreenDeployment/BlueGreenDeploymentForm.tsx b/packages/amazon/src/pipeline/stages/routeLambda/components/BlueGreenDeployment/BlueGreenDeploymentForm.tsx new file mode 100644 index 00000000000..7a21197befa --- /dev/null +++ b/packages/amazon/src/pipeline/stages/routeLambda/components/BlueGreenDeployment/BlueGreenDeploymentForm.tsx @@ -0,0 +1,27 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; + +import type { IFormikStageConfigInjectedProps, IFormInputProps } from '@spinnaker/core'; +import { FormikFormField, ReactSelectInput } from '@spinnaker/core'; + +import { retrieveHealthCheck } from './HealthCheckStrategy'; +import { HealthCheckList } from './health.constants'; + +export function BlueGreenDeploymentForm(props: IFormikStageConfigInjectedProps) { + const { values } = props.formik; + + return ( +
+ ( + + )} + /> + {values.healthCheckType ? retrieveHealthCheck(values.healthCheckType, props) : null} +
+ ); +} diff --git a/packages/amazon/src/pipeline/stages/routeLambda/components/BlueGreenDeployment/HealthCheckStrategy.tsx b/packages/amazon/src/pipeline/stages/routeLambda/components/BlueGreenDeployment/HealthCheckStrategy.tsx new file mode 100644 index 00000000000..bc37b210f0b --- /dev/null +++ b/packages/amazon/src/pipeline/stages/routeLambda/components/BlueGreenDeployment/HealthCheckStrategy.tsx @@ -0,0 +1,21 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; + +import type { IFormikStageConfigInjectedProps } from '@spinnaker/core'; + +import { InvokeLambdaHealthCheck } from './InvocationHealthCheck'; + +export function retrieveHealthCheck(value: string, props: IFormikStageConfigInjectedProps) { + switch (value) { + case '$LAMBDA': + return ; + case '$WEIGHTED': + return null; + case '$BLUEGREEN': + return null; + default: + return null; + } +} diff --git a/packages/amazon/src/pipeline/stages/routeLambda/components/BlueGreenDeployment/InvocationHealthCheck.tsx b/packages/amazon/src/pipeline/stages/routeLambda/components/BlueGreenDeployment/InvocationHealthCheck.tsx new file mode 100644 index 00000000000..a229bbc1686 --- /dev/null +++ b/packages/amazon/src/pipeline/stages/routeLambda/components/BlueGreenDeployment/InvocationHealthCheck.tsx @@ -0,0 +1,93 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; + +import type { IArtifact, IExpectedArtifact, IFormikStageConfigInjectedProps } from '@spinnaker/core'; +import { + ArtifactTypePatterns, + CheckboxInput, + excludeAllTypesExcept, + FormikFormField, + NumberInput, + StageArtifactSelectorDelegate, +} from '@spinnaker/core'; + +export function InvokeLambdaHealthCheck(props: IFormikStageConfigInjectedProps) { + const { values } = props.formik; + + const excludedArtifactTypes = excludeAllTypesExcept( + ArtifactTypePatterns.BITBUCKET_FILE, + ArtifactTypePatterns.CUSTOM_OBJECT, + ArtifactTypePatterns.EMBEDDED_BASE64, + ArtifactTypePatterns.GCS_OBJECT, + ArtifactTypePatterns.GITHUB_FILE, + ArtifactTypePatterns.GITLAB_FILE, + ArtifactTypePatterns.S3_OBJECT, + ArtifactTypePatterns.HTTP_FILE, + ); + + const onTemplateArtifactEdited = (artifact: IArtifact, name: string) => { + props.formik.setFieldValue(`${name}.id`, null); + props.formik.setFieldValue(`${name}.artifact`, artifact); + props.formik.setFieldValue(`${name}.account`, artifact.artifactAccount); + }; + + const onTemplateArtifactSelected = (id: string, name: string) => { + props.formik.setFieldValue(`${name}.id`, id); + props.formik.setFieldValue(`${name}.artifact`, null); + }; + + const getInputArtifact = (stage: any, name: string) => { + if (!stage[name]) { + return { + account: '', + id: '', + }; + } else { + return stage[name]; + } + }; + + return ( +
+ } + /> + } /> + + { + onTemplateArtifactEdited(artifact, 'payloadArtifact'); + }} + helpKey={''} + onExpectedArtifactSelected={(artifact: IExpectedArtifact) => + onTemplateArtifactSelected(artifact.id, 'payloadrtifact') + } + pipeline={props.pipeline} + stage={values} + /> + { + onTemplateArtifactEdited(artifact, 'outputArtifact'); + }} + helpKey={''} + onExpectedArtifactSelected={(artifact: IExpectedArtifact) => + onTemplateArtifactSelected(artifact.id, 'outputArtifact') + } + pipeline={props.pipeline} + stage={values} + /> +
+ ); +} diff --git a/packages/amazon/src/pipeline/stages/routeLambda/components/BlueGreenDeployment/health.constants.ts b/packages/amazon/src/pipeline/stages/routeLambda/components/BlueGreenDeployment/health.constants.ts new file mode 100644 index 00000000000..559185e1c3d --- /dev/null +++ b/packages/amazon/src/pipeline/stages/routeLambda/components/BlueGreenDeployment/health.constants.ts @@ -0,0 +1,14 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export interface IHealthConstant { + label: string; + value: string; +} + +export const HealthCheckList: IHealthConstant[] = [ + { + label: 'Lambda Invocation', + value: '$LAMBDA', + }, +]; diff --git a/packages/amazon/src/pipeline/stages/routeLambda/components/BlueGreenDeployment/index.ts b/packages/amazon/src/pipeline/stages/routeLambda/components/BlueGreenDeployment/index.ts new file mode 100644 index 00000000000..7af96d23f00 --- /dev/null +++ b/packages/amazon/src/pipeline/stages/routeLambda/components/BlueGreenDeployment/index.ts @@ -0,0 +1,4 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export * from './BlueGreenDeploymentForm'; diff --git a/packages/amazon/src/pipeline/stages/routeLambda/components/DeploymentStrategyForm.tsx b/packages/amazon/src/pipeline/stages/routeLambda/components/DeploymentStrategyForm.tsx new file mode 100644 index 00000000000..f3b6f5aa05c --- /dev/null +++ b/packages/amazon/src/pipeline/stages/routeLambda/components/DeploymentStrategyForm.tsx @@ -0,0 +1,18 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; + +import type { IFormikStageConfigInjectedProps } from '@spinnaker/core'; + +import { retrieveComponent } from './RenderStrategy'; + +export function DeploymentStrategyForm(props: IFormikStageConfigInjectedProps) { + const { values } = props.formik; + + return ( +
+ {values.deploymentStrategy ? retrieveComponent(values.deploymentStrategy, props) : null} +
+ ); +} diff --git a/packages/amazon/src/pipeline/stages/routeLambda/components/RenderStrategy.tsx b/packages/amazon/src/pipeline/stages/routeLambda/components/RenderStrategy.tsx new file mode 100644 index 00000000000..0f5a6759a05 --- /dev/null +++ b/packages/amazon/src/pipeline/stages/routeLambda/components/RenderStrategy.tsx @@ -0,0 +1,23 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; + +import type { IFormikStageConfigInjectedProps } from '@spinnaker/core'; + +import { BlueGreenDeploymentForm } from './BlueGreenDeployment'; +import { SimpleDeploymentForm } from './SimpleDeploymentForm'; +import { WeightedDeploymentForm } from './WeightedDeploymentForm'; + +export function retrieveComponent(value: string, props: IFormikStageConfigInjectedProps) { + switch (value) { + case '$SIMPLE': + return ; + case '$WEIGHTED': + return ; + case '$BLUEGREEN': + return ; + default: + return null; + } +} diff --git a/packages/amazon/src/pipeline/stages/routeLambda/components/SimpleDeploymentForm.tsx b/packages/amazon/src/pipeline/stages/routeLambda/components/SimpleDeploymentForm.tsx new file mode 100644 index 00000000000..f48328d18b4 --- /dev/null +++ b/packages/amazon/src/pipeline/stages/routeLambda/components/SimpleDeploymentForm.tsx @@ -0,0 +1,67 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; + +import type { IFormikStageConfigInjectedProps, IFormInputProps } from '@spinnaker/core'; +import { FormikFormField, NumberInput, ReactSelectInput } from '@spinnaker/core'; + +import { VersionPicker } from './VersionPicker'; +import { VersionList } from '../constants'; +import type { IAmazonFunctionSourceData } from '../../../../domain'; + +export function SimpleDeploymentForm(props: IFormikStageConfigInjectedProps) { + const { values } = props.formik; + const { functions } = props.application; + + const onVersionChange = (fieldValue: any): void => { + props.formik.setFieldValue('trafficPercentA', 100); + props.formik.setFieldValue('versionNameA', fieldValue); + }; + + return ( +
+ ( + } + /> + )} + /> + {values.versionNameA === '$PROVIDED' ? ( + ( + f.account === values.account) + .filter((f: IAmazonFunctionSourceData) => f.region === values.region) + .filter((f: IAmazonFunctionSourceData) => f.functionName === values.functionName) + .flatMap((f: IAmazonFunctionSourceData) => + Object.values(f.revisions).sort(function (a: number, b: number) { + return b - a; + }), + ) + .filter((r: any) => r !== '$LATEST')} + /> + )} + /> + ) : null} + + } + /> +
+ ); +} diff --git a/packages/amazon/src/pipeline/stages/routeLambda/components/VersionPicker.tsx b/packages/amazon/src/pipeline/stages/routeLambda/components/VersionPicker.tsx new file mode 100644 index 00000000000..6fdf954130d --- /dev/null +++ b/packages/amazon/src/pipeline/stages/routeLambda/components/VersionPicker.tsx @@ -0,0 +1,43 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; + +import type { IVersionConstant } from '../constants'; +import { VersionList } from '../constants'; + +export interface IVersionPickerProps { + value: string; + showingDetails: boolean; +} + +export interface IVersionPickerState { + value: string; + label: string; + description: string; +} + +export class VersionPicker extends React.Component { + constructor(props: IVersionPickerProps) { + super(props); + + // In here we will link the 'value' to an actual version id - when this is passed to Orca it will have the ID + + const { value } = this.props; + const versionDetails = VersionList.filter((v: IVersionConstant) => v.value === value)[0]; + this.state = { + label: versionDetails.label, + value: versionDetails.value, + description: versionDetails.description, + }; + } + + public render() { + return ( +
+ {this.state.label}
+ {this.state.description} +
+ ); + } +} diff --git a/packages/amazon/src/pipeline/stages/routeLambda/components/WeightedDeploymentForm.tsx b/packages/amazon/src/pipeline/stages/routeLambda/components/WeightedDeploymentForm.tsx new file mode 100644 index 00000000000..f8f03b0dba9 --- /dev/null +++ b/packages/amazon/src/pipeline/stages/routeLambda/components/WeightedDeploymentForm.tsx @@ -0,0 +1,107 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import React from 'react'; + +import type { IFormikStageConfigInjectedProps, IFormInputProps } from '@spinnaker/core'; +import { FormikFormField, NumberInput, ReactSelectInput } from '@spinnaker/core'; + +import { VersionPicker } from './VersionPicker'; +import { VersionList } from '../constants'; +import type { IAmazonFunctionSourceData } from '../../../../domain'; + +export function WeightedDeploymentForm(props: IFormikStageConfigInjectedProps) { + const { values } = props.formik; + const { functions } = props.application; + + return ( +
+ ( + } + /> + )} + /> + {values.versionNameA === '$PROVIDED' ? ( + ( + f.account === values.account) + .filter((f: IAmazonFunctionSourceData) => f.region === values.region) + .filter((f: IAmazonFunctionSourceData) => f.functionName === values.functionName) + .flatMap((f: IAmazonFunctionSourceData) => + Object.values(f.revisions).sort(function (a: number, b: number) { + return b - a; + }), + ) + .filter((r: any) => r !== '$LATEST')} + /> + )} + /> + ) : null} + } + /> + + ( + } + /> + )} + /> + {values.versionNameB === '$PROVIDED' ? ( + ( + f.account === values.account) + .filter((f: IAmazonFunctionSourceData) => f.region === values.region) + .filter((f: IAmazonFunctionSourceData) => f.functionName === values.functionName) + .flatMap((f: IAmazonFunctionSourceData) => + Object.values(f.revisions).sort(function (a: number, b: number) { + return b - a; + }), + ) + .filter((r: any) => r !== '$LATEST')} + /> + )} + /> + ) : null} + ( + + )} + /> +
+ ); +} diff --git a/packages/amazon/src/pipeline/stages/routeLambda/components/index.ts b/packages/amazon/src/pipeline/stages/routeLambda/components/index.ts new file mode 100644 index 00000000000..2e67e40691d --- /dev/null +++ b/packages/amazon/src/pipeline/stages/routeLambda/components/index.ts @@ -0,0 +1,4 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export * from './DeploymentStrategyForm'; diff --git a/packages/amazon/src/pipeline/stages/routeLambda/constants/index.ts b/packages/amazon/src/pipeline/stages/routeLambda/constants/index.ts new file mode 100644 index 00000000000..48dd87f71e9 --- /dev/null +++ b/packages/amazon/src/pipeline/stages/routeLambda/constants/index.ts @@ -0,0 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export * from './strategy.constants'; +export * from './strategy.picker'; +export * from './versions.constants'; diff --git a/packages/amazon/src/pipeline/stages/routeLambda/constants/strategy.constants.tsx b/packages/amazon/src/pipeline/stages/routeLambda/constants/strategy.constants.tsx new file mode 100644 index 00000000000..c10ae5f05f2 --- /dev/null +++ b/packages/amazon/src/pipeline/stages/routeLambda/constants/strategy.constants.tsx @@ -0,0 +1,26 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export interface IStrategyConstant { + description: string; + label: string; + value: string; +} + +export const DeploymentStrategyList: IStrategyConstant[] = [ + { + label: 'Simple', + value: '$SIMPLE', + description: 'Route 100% of traffic to specified version', + }, + { + label: 'Weighted Deployment', + value: '$WEIGHTED', + description: 'Split the traffic weight between two function versions.', + }, + { + label: 'Blue/Green', + value: '$BLUEGREEN', + description: 'Disable all previous versions once the latest version passes health checks.', + }, +]; diff --git a/packages/amazon/src/pipeline/stages/routeLambda/constants/strategy.picker.tsx b/packages/amazon/src/pipeline/stages/routeLambda/constants/strategy.picker.tsx new file mode 100644 index 00000000000..cfede88fcc1 --- /dev/null +++ b/packages/amazon/src/pipeline/stages/routeLambda/constants/strategy.picker.tsx @@ -0,0 +1,47 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; + +import type { IFormikStageConfigInjectedProps } from '@spinnaker/core'; + +import type { IStrategyConstant } from './strategy.constants'; +import { DeploymentStrategyList } from './strategy.constants'; + +export interface IVersionPickerProps { + config: IFormikStageConfigInjectedProps; + value: string; + showingDetails: boolean; +} + +export interface IVersionPickerState { + value: string; + label: string; + description: string; +} + +export class DeploymentStrategyPicker extends React.Component { + constructor(props: IVersionPickerProps) { + super(props); + + const { value } = this.props; + + const strategyDetails = DeploymentStrategyList.filter((v: IStrategyConstant) => v.value === value)[0]; + + this.state = { + label: strategyDetails.label, + value: strategyDetails.value, + description: strategyDetails.description, + }; + } + + public render() { + return ( +
+ {this.state.label} +
+ {this.state.description} +
+ ); + } +} diff --git a/packages/amazon/src/pipeline/stages/routeLambda/constants/versions.constants.ts b/packages/amazon/src/pipeline/stages/routeLambda/constants/versions.constants.ts new file mode 100644 index 00000000000..55c43b62f3e --- /dev/null +++ b/packages/amazon/src/pipeline/stages/routeLambda/constants/versions.constants.ts @@ -0,0 +1,31 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export interface IVersionConstant { + description: string; + label: string; + value: string; +} + +export const VersionList: IVersionConstant[] = [ + { + label: 'Newest Function Version', + value: '$LATEST', + description: 'Selects the most recently deployed function when this stage starts.', + }, + { + label: 'Previous Function Version', + value: '$PREVIOUS', + description: 'Selects the second-most recently deployed function when this stage starts.', + }, + { + label: 'Oldest Function Verion', + value: '$OLDEST', + description: 'Selects the least recently deployed function when this stage starts.', + }, + { + label: 'Provide Version Number', + value: '$PROVIDED', + description: 'Provide a specific version number to destroy.', + }, +]; diff --git a/packages/amazon/src/pipeline/stages/routeLambda/index.ts b/packages/amazon/src/pipeline/stages/routeLambda/index.ts new file mode 100644 index 00000000000..154d3d60cb2 --- /dev/null +++ b/packages/amazon/src/pipeline/stages/routeLambda/index.ts @@ -0,0 +1,11 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Registry, SETTINGS } from '@spinnaker/core'; +import { lambdaRouteStage } from './LambdaRouteStage'; + +export * from './LambdaRouteStage'; + +if (SETTINGS.feature.lambdaAdditionalStages) { + Registry.pipeline.registerStage(lambdaRouteStage); +} diff --git a/packages/amazon/src/pipeline/stages/updateCodeLambda/LambdaUpdateCodeStage.tsx b/packages/amazon/src/pipeline/stages/updateCodeLambda/LambdaUpdateCodeStage.tsx new file mode 100644 index 00000000000..a0a12e512f3 --- /dev/null +++ b/packages/amazon/src/pipeline/stages/updateCodeLambda/LambdaUpdateCodeStage.tsx @@ -0,0 +1,20 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import type { IStageTypeConfig } from '@spinnaker/core'; +import { ExecutionDetailsTasks, HelpContentsRegistry } from '@spinnaker/core'; + +import { LambdaUpdateCodeConfig, validate } from './LambdaUpdateCodeStageConfig'; +import { LambdaUpdateCodeExecutionDetails } from './LambdaUpdateCodeStageExecutionDetails'; + +export const initialize = () => { + HelpContentsRegistry.register('aws.lambdaDeploymentStage.lambda', 'Lambda Name'); +}; + +export const lambdaUpdateCodeStage: IStageTypeConfig = { + key: 'Aws.LambdaUpdateCodeStage', + label: `AWS Lambda Update Code`, + description: 'Update code for a single AWS Lambda Function', + component: LambdaUpdateCodeConfig, // stage config + executionDetailsSections: [LambdaUpdateCodeExecutionDetails, ExecutionDetailsTasks], + validateFn: validate, +}; diff --git a/packages/amazon/src/pipeline/stages/updateCodeLambda/LambdaUpdateCodeStageConfig.tsx b/packages/amazon/src/pipeline/stages/updateCodeLambda/LambdaUpdateCodeStageConfig.tsx new file mode 100644 index 00000000000..d56483e11e1 --- /dev/null +++ b/packages/amazon/src/pipeline/stages/updateCodeLambda/LambdaUpdateCodeStageConfig.tsx @@ -0,0 +1,41 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; + +import type { IFormikStageConfigInjectedProps, IStage, IStageConfigProps } from '@spinnaker/core'; +import { FormikStageConfig, FormValidator } from '@spinnaker/core'; +import { s3BucketNameValidator } from '../../../aws.validators'; + +import { UpdateCodeLambdaFunctionStageForm } from './components'; + +import './LambdaUpdateCodeStage.less'; + +export function LambdaUpdateCodeConfig(props: IStageConfigProps) { + return ( +
+ } + /> +
+ ); +} + +export function validate(stageConfig: IStage) { + const validator = new FormValidator(stageConfig); + + validator.field('account', 'Account Name').required(); + + validator.field('region', 'Region').required(); + + validator.field('functionName', 'Lambda Function Name').required(); + + validator.field('s3key', 'S3 Object Key').required(); + + validator.field('s3bucket', 'S3 Bucket Name').required().withValidators(s3BucketNameValidator); + + return validator.validateForm(); +} diff --git a/packages/amazon/src/pipeline/stages/updateCodeLambda/LambdaUpdateCodeStageExecutionDetails.tsx b/packages/amazon/src/pipeline/stages/updateCodeLambda/LambdaUpdateCodeStageExecutionDetails.tsx new file mode 100644 index 00000000000..f5369f9602a --- /dev/null +++ b/packages/amazon/src/pipeline/stages/updateCodeLambda/LambdaUpdateCodeStageExecutionDetails.tsx @@ -0,0 +1,31 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; + +import type { IExecutionDetailsSectionProps } from '@spinnaker/core'; +import { ExecutionDetailsSection, StageFailureMessage } from '@spinnaker/core'; + +export function LambdaUpdateCodeExecutionDetails(props: IExecutionDetailsSectionProps) { + const { stage, current, name } = props; + return ( + + +
+

+ {' '} + Function Name: {stage.outputs.functionName ? stage.outputs.functionName : 'N/A'}{' '} +

+

+ {' '} + Function ARN: {stage.outputs.functionARN ? stage.outputs.functionARN : 'N/A'}{' '} +

+
+
+ ); +} + +// eslint-disable-next-line +export namespace LambdaUpdateCodeExecutionDetails { + export const title = 'Lambda Update Code Stage'; +} diff --git a/packages/amazon/src/pipeline/stages/updateCodeLambda/components/UpdateCodeStageForm.tsx b/packages/amazon/src/pipeline/stages/updateCodeLambda/components/UpdateCodeStageForm.tsx new file mode 100644 index 00000000000..c2fcf423c8c --- /dev/null +++ b/packages/amazon/src/pipeline/stages/updateCodeLambda/components/UpdateCodeStageForm.tsx @@ -0,0 +1,122 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; + +import type { + IAccount, + IAccountDetails, + IFormikStageConfigInjectedProps, + IFormInputProps, + IFunction, + IRegion, +} from '@spinnaker/core'; +import { + AccountService, + CheckboxInput, + FormikFormField, + HelpField, + ReactSelectInput, + TextInput, + useData, +} from '@spinnaker/core'; + +export function UpdateCodeLambdaFunctionStageForm(props: IFormikStageConfigInjectedProps) { + const { values } = props.formik; + const { functions } = props.application; + + const { result: fetchAccountsResult, status: fetchAccountsStatus } = useData( + () => AccountService.listAccounts('aws'), + [], + [], + ); + + const onAccountChange = (fieldValue: any): void => { + props.formik.setFieldValue('region', null); + props.formik.setFieldValue('functionName', null); + + props.formik.setFieldValue('account', fieldValue); + }; + + const onRegionChange = (fieldValue: any): void => { + props.formik.setFieldValue('functionName', null); + props.formik.setFieldValue('region', fieldValue); + }; + + const availableFunctions = + values.account && values.region + ? functions.data + .filter((f: IFunction) => f.account === values.account) + .filter((f: IFunction) => f.region === values.region) + .map((f: IFunction) => f.functionName) + : []; + + return ( +
+

Basic Settings

+ ( + acc.name)} + /> + )} + /> + ( + acc.name === values.account) + .flatMap((acc: IAccountDetails) => acc.regions) + .map((reg: IRegion) => reg.name)} + /> + )} + /> + ( + + )} + /> + } + input={(props) => } + /> + } + input={(props) => } + /> + } + input={(props) => } + /> +
+ ); +} diff --git a/packages/amazon/src/pipeline/stages/updateCodeLambda/components/index.ts b/packages/amazon/src/pipeline/stages/updateCodeLambda/components/index.ts new file mode 100644 index 00000000000..77613225d78 --- /dev/null +++ b/packages/amazon/src/pipeline/stages/updateCodeLambda/components/index.ts @@ -0,0 +1,4 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export * from './UpdateCodeStageForm'; diff --git a/packages/amazon/src/pipeline/stages/updateCodeLambda/index.ts b/packages/amazon/src/pipeline/stages/updateCodeLambda/index.ts new file mode 100644 index 00000000000..f8f0dccc76a --- /dev/null +++ b/packages/amazon/src/pipeline/stages/updateCodeLambda/index.ts @@ -0,0 +1,11 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Registry, SETTINGS } from '@spinnaker/core'; +import { lambdaUpdateCodeStage } from './LambdaUpdateCodeStage'; + +export * from './LambdaUpdateCodeStage'; + +if (SETTINGS.feature.lambdaAdditionalStages) { + Registry.pipeline.registerStage(lambdaUpdateCodeStage); +} diff --git a/packages/amazon/src/serverGroup/details/scalingPolicy/upsert/step/StepPolicyAction.tsx b/packages/amazon/src/serverGroup/details/scalingPolicy/upsert/step/StepPolicyAction.tsx index 0be68fedeaf..215e362e5d8 100644 --- a/packages/amazon/src/serverGroup/details/scalingPolicy/upsert/step/StepPolicyAction.tsx +++ b/packages/amazon/src/serverGroup/details/scalingPolicy/upsert/step/StepPolicyAction.tsx @@ -106,8 +106,9 @@ export const StepPolicyAction = ({ - updateStep({ ...step, metricIntervalLowerBound: Number.parseInt(e.target.value) }, index) + updateStep({ ...step, metricIntervalLowerBound: Number.parseFloat(e.target.value) }, index) } inputClassName="action-input" /> @@ -121,8 +122,9 @@ export const StepPolicyAction = ({ - updateStep({ ...step, metricIntervalUpperBound: Number.parseInt(e.target.value) }, index) + updateStep({ ...step, metricIntervalUpperBound: Number.parseFloat(e.target.value) }, index) } inputClassName="action-input" /> diff --git a/packages/amazon/src/serverGroup/serverGroup.transformer.spec.ts b/packages/amazon/src/serverGroup/serverGroup.transformer.spec.ts index 9236d73ab62..507d304e1e2 100644 --- a/packages/amazon/src/serverGroup/serverGroup.transformer.spec.ts +++ b/packages/amazon/src/serverGroup/serverGroup.transformer.spec.ts @@ -169,6 +169,17 @@ describe('awsServerGroupTransformer', () => { [0, 10, -5], ); }); + + it('verify float adjustments work within the range', function () { + this.test( + [ + { id: 1, scalingAdjustment: 10, metricIntervalLowerBound: 3.5, metricIntervalUpperBound: 5.5 }, + { id: 2, scalingAdjustment: 0, metricIntervalLowerBound: 5.5 }, + { id: 3, scalingAdjustment: -5, metricIntervalLowerBound: 1.2, metricIntervalUpperBound: 3.5 }, + ], + [-5, 10, 0], + ); + }); }); }); }); diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index 6a4e7980e02..e38a95af329 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -3,6 +3,44 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.7.0](https://github.com/spinnaker/deck/compare/deck-app@2.6.0...deck-app@2.7.0) (2023-12-07) + + +### Features + +* Add feature flag for multi block failure messages. ([#10061](https://github.com/spinnaker/deck/issues/10061)) ([374f724](https://github.com/spinnaker/deck/commit/374f724de221d68030a86e1f6452e3303390339a)) + + + + + +# [2.6.0](https://github.com/spinnaker/deck/compare/deck-app@2.5.7...deck-app@2.6.0) (2023-10-16) + + +### Features + +* **helm/bake:** Add additional input fields where we can fill in details of the APIs versions ([#10036](https://github.com/spinnaker/deck/issues/10036)) ([d968183](https://github.com/spinnaker/deck/commit/d9681830244ecd1c70cc02459f148d0822b7187e)) + + + + + +## [2.5.7](https://github.com/spinnaker/deck/compare/deck-app@2.5.6...deck-app@2.5.7) (2023-09-06) + +**Note:** Version bump only for package deck-app + + + + + +## [2.5.6](https://github.com/spinnaker/deck/compare/deck-app@2.5.5...deck-app@2.5.6) (2023-07-20) + +**Note:** Version bump only for package deck-app + + + + + ## [2.5.5](https://github.com/spinnaker/deck/compare/deck-app@2.5.4...deck-app@2.5.5) (2023-06-02) **Note:** Version bump only for package deck-app diff --git a/packages/app/package.json b/packages/app/package.json index ac8b9f83878..90b1a5ca1ff 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,7 +1,7 @@ { "name": "deck-app", "private": true, - "version": "2.5.5", + "version": "2.7.0", "description": "", "main": "index.js", "scripts": { @@ -14,19 +14,19 @@ "author": "", "license": "ISC", "dependencies": { - "@spinnaker/amazon": "^0.13.9", - "@spinnaker/appengine": "^0.1.7", - "@spinnaker/cloudfoundry": "^0.1.7", - "@spinnaker/cloudrun": "^0.1.3", - "@spinnaker/core": "^0.24.1", - "@spinnaker/docker": "^0.0.141", - "@spinnaker/ecs": "^0.0.360", - "@spinnaker/google": "^0.2.8", - "@spinnaker/kayenta": "2.0.0", - "@spinnaker/kubernetes": "^0.4.4", - "@spinnaker/oracle": "^0.0.85", + "@spinnaker/amazon": "^0.14.3", + "@spinnaker/appengine": "^0.1.11", + "@spinnaker/cloudfoundry": "^0.1.11", + "@spinnaker/cloudrun": "^0.1.7", + "@spinnaker/core": "^0.28.0", + "@spinnaker/docker": "^0.0.145", + "@spinnaker/ecs": "^0.0.364", + "@spinnaker/google": "^0.2.12", + "@spinnaker/kayenta": "2.1.0", + "@spinnaker/kubernetes": "^0.6.0", + "@spinnaker/oracle": "^0.0.89", "@spinnaker/styleguide": "^2.0.0", - "@spinnaker/titus": "^0.5.38", + "@spinnaker/titus": "^0.5.42", "@uirouter/angularjs": "1.0.26", "angular": "1.8.0", "graphql": "15.5.0", @@ -75,7 +75,7 @@ "thread-loader": "2.1.2", "ts-loader": "8.0.3", "typescript": "4.3.5", - "vite": "2.4.2", + "vite": "2.9.16", "vite-plugin-html-config": "^1.0.5", "vite-plugin-svgr": "^0.3.0", "webpack": "4.44.2", diff --git a/packages/app/src/settings.js b/packages/app/src/settings.js index c860d7b02a7..d22aa9ea513 100644 --- a/packages/app/src/settings.js +++ b/packages/app/src/settings.js @@ -51,12 +51,18 @@ const managedServiceAccountsEnabled = const managedResourcesEnabled = import.meta.env.VITE_MANAGED_RESOURCES_ENABLED === 'true' || process.env.MANAGED_RESOURCES_ENABLED === 'true'; const manualJudgmentParentPipelineEnabled = import.meta.env.MJ_PARENTPIPELINE_ENABLED !== 'false'; +const multiBlockFailureMessagesEnabled = + import.meta.env.MULTI_BLOCK_FAILURE_MESSAGES_ENABLED === 'true' || + process.env.MULTI_BLOCK_FAILURE_MESSAGES_ENABLED === 'true' || + 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'; const templatesEnabled = import.meta.env.VITE_TEMPLATES_ENABLED === 'true' || process.env.TEMPLATES_ENABLED === 'true'; const useClassicFirewallLabels = import.meta.env.VITE_USE_CLASSIC_FIREWALL_LABELS === 'true' || process.env.USE_CLASSIC_FIREWALL_LABELS === 'true'; +const helmApiVersionsEnabled = + import.meta.env.VITE_API_VERSIONS_ENABLED === 'true' || process.env.API_VERSIONS_ENABLED === 'true' || false; const functionsEnabled = import.meta.env.VITE_FUNCTIONS_ENABLED === 'true' || process.env.FUNCTIONS_ENABLED === 'true' || false; const k8sRawResourcesEnabled = @@ -121,6 +127,7 @@ window.spinnakerSettings = { managedServiceAccounts: managedServiceAccountsEnabled, managedResources: managedResourcesEnabled, manualJudgmentParentPipeline: manualJudgmentParentPipelineEnabled, + multiBlockFailureMessages: multiBlockFailureMessagesEnabled, dynamicRollbackTimeout: dynamicRollbackTimeoutEnabled, notifications: false, pagerDuty: false, @@ -131,6 +138,7 @@ window.spinnakerSettings = { slack: false, snapshots: false, functions: functionsEnabled, + helmApiVersions: helmApiVersionsEnabled, kubernetesRawResources: k8sRawResourcesEnabled, }, gateUrl: apiHost, diff --git a/packages/appengine/CHANGELOG.md b/packages/appengine/CHANGELOG.md index 81f397a110c..2a03fb95c58 100644 --- a/packages/appengine/CHANGELOG.md +++ b/packages/appengine/CHANGELOG.md @@ -3,6 +3,41 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.11](https://github.com/spinnaker/deck/compare/@spinnaker/appengine@0.1.10...@spinnaker/appengine@0.1.11) (2023-12-07) + +**Note:** Version bump only for package @spinnaker/appengine + + + + + +## [0.1.10](https://github.com/spinnaker/deck/compare/@spinnaker/appengine@0.1.9...@spinnaker/appengine@0.1.10) (2023-10-16) + + +### Bug Fixes + +* **publish:** set access config in deck libraries ([#10049](https://github.com/spinnaker/deck/issues/10049)) ([2a5ebe2](https://github.com/spinnaker/deck/commit/2a5ebe25662eeb9d41b5071749266bf9d6d51104)) + + + + + +## [0.1.9](https://github.com/spinnaker/deck/compare/@spinnaker/appengine@0.1.8...@spinnaker/appengine@0.1.9) (2023-09-06) + +**Note:** Version bump only for package @spinnaker/appengine + + + + + +## [0.1.8](https://github.com/spinnaker/deck/compare/@spinnaker/appengine@0.1.7...@spinnaker/appengine@0.1.8) (2023-07-20) + +**Note:** Version bump only for package @spinnaker/appengine + + + + + ## [0.1.7](https://github.com/spinnaker/deck/compare/@spinnaker/appengine@0.1.6...@spinnaker/appengine@0.1.7) (2023-06-02) **Note:** Version bump only for package @spinnaker/appengine diff --git a/packages/appengine/package.json b/packages/appengine/package.json index e6f95627c91..3fdb8f4c105 100644 --- a/packages/appengine/package.json +++ b/packages/appengine/package.json @@ -1,9 +1,12 @@ { "name": "@spinnaker/appengine", "license": "Apache-2.0", - "version": "0.1.7", + "version": "0.1.11", "module": "dist/index.js", "typings": "dist/index.d.ts", + "publishConfig": { + "access": "public" + }, "scripts": { "clean": "shx rm -rf dist", "prepublishOnly": "npm run build", @@ -13,7 +16,7 @@ "lib": "npm run build" }, "dependencies": { - "@spinnaker/core": "^0.24.1", + "@spinnaker/core": "^0.28.0", "@uirouter/angularjs": "1.0.26", "angular": "1.6.10", "angular-ui-bootstrap": "2.5.0", @@ -26,9 +29,9 @@ "rxjs": "6.6.7" }, "devDependencies": { - "@spinnaker/eslint-plugin": "^3.0.1", + "@spinnaker/eslint-plugin": "^3.0.2", "@spinnaker/mocks": "1.0.7", - "@spinnaker/scripts": "^0.3.0", + "@spinnaker/scripts": "^0.4.0", "@types/angular": "1.6.26", "@types/angular-ui-bootstrap": "0.13.41", "@types/classnames": "2.2.0", diff --git a/packages/azure/CHANGELOG.md b/packages/azure/CHANGELOG.md index bc226e65855..974275469cf 100644 --- a/packages/azure/CHANGELOG.md +++ b/packages/azure/CHANGELOG.md @@ -3,6 +3,41 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.4.8](https://github.com/spinnaker/deck/compare/@spinnaker/azure@0.4.7...@spinnaker/azure@0.4.8) (2023-12-07) + +**Note:** Version bump only for package @spinnaker/azure + + + + + +## [0.4.7](https://github.com/spinnaker/deck/compare/@spinnaker/azure@0.4.6...@spinnaker/azure@0.4.7) (2023-10-16) + + +### Bug Fixes + +* **publish:** set access config in deck libraries ([#10049](https://github.com/spinnaker/deck/issues/10049)) ([2a5ebe2](https://github.com/spinnaker/deck/commit/2a5ebe25662eeb9d41b5071749266bf9d6d51104)) + + + + + +## [0.4.6](https://github.com/spinnaker/deck/compare/@spinnaker/azure@0.4.5...@spinnaker/azure@0.4.6) (2023-09-06) + +**Note:** Version bump only for package @spinnaker/azure + + + + + +## [0.4.5](https://github.com/spinnaker/deck/compare/@spinnaker/azure@0.4.4...@spinnaker/azure@0.4.5) (2023-07-20) + +**Note:** Version bump only for package @spinnaker/azure + + + + + ## [0.4.4](https://github.com/spinnaker/deck/compare/@spinnaker/azure@0.4.3...@spinnaker/azure@0.4.4) (2023-06-02) **Note:** Version bump only for package @spinnaker/azure diff --git a/packages/azure/package.json b/packages/azure/package.json index 76972ad58c0..11652890deb 100644 --- a/packages/azure/package.json +++ b/packages/azure/package.json @@ -1,9 +1,12 @@ { "name": "@spinnaker/azure", "license": "Apache-2.0", - "version": "0.4.4", + "version": "0.4.8", "module": "dist/index.js", "typings": "dist/index.d.ts", + "publishConfig": { + "access": "public" + }, "scripts": { "clean": "shx rm -rf dist", "prepublishOnly": "npm run build", @@ -13,7 +16,7 @@ "lib": "npm run build" }, "dependencies": { - "@spinnaker/core": "^0.24.1", + "@spinnaker/core": "^0.28.0", "@uirouter/angularjs": "1.0.26", "angular": "1.6.10", "angular-ui-bootstrap": "2.5.0", @@ -23,8 +26,8 @@ "react-select": "1.2.1" }, "devDependencies": { - "@spinnaker/eslint-plugin": "^3.0.1", - "@spinnaker/scripts": "^0.3.0", + "@spinnaker/eslint-plugin": "^3.0.2", + "@spinnaker/scripts": "^0.4.0", "@types/angular": "1.6.26", "@types/angular-ui-bootstrap": "0.13.41", "@types/lodash": "4.14.64", diff --git a/packages/cloudfoundry/CHANGELOG.md b/packages/cloudfoundry/CHANGELOG.md index e04b1fa479a..d079a1eb7b5 100644 --- a/packages/cloudfoundry/CHANGELOG.md +++ b/packages/cloudfoundry/CHANGELOG.md @@ -3,6 +3,41 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.11](https://github.com/spinnaker/deck/compare/@spinnaker/cloudfoundry@0.1.10...@spinnaker/cloudfoundry@0.1.11) (2023-12-07) + +**Note:** Version bump only for package @spinnaker/cloudfoundry + + + + + +## [0.1.10](https://github.com/spinnaker/deck/compare/@spinnaker/cloudfoundry@0.1.9...@spinnaker/cloudfoundry@0.1.10) (2023-10-16) + + +### Bug Fixes + +* **publish:** set access config in deck libraries ([#10049](https://github.com/spinnaker/deck/issues/10049)) ([2a5ebe2](https://github.com/spinnaker/deck/commit/2a5ebe25662eeb9d41b5071749266bf9d6d51104)) + + + + + +## [0.1.9](https://github.com/spinnaker/deck/compare/@spinnaker/cloudfoundry@0.1.8...@spinnaker/cloudfoundry@0.1.9) (2023-09-06) + +**Note:** Version bump only for package @spinnaker/cloudfoundry + + + + + +## [0.1.8](https://github.com/spinnaker/deck/compare/@spinnaker/cloudfoundry@0.1.7...@spinnaker/cloudfoundry@0.1.8) (2023-07-20) + +**Note:** Version bump only for package @spinnaker/cloudfoundry + + + + + ## [0.1.7](https://github.com/spinnaker/deck/compare/@spinnaker/cloudfoundry@0.1.6...@spinnaker/cloudfoundry@0.1.7) (2023-06-02) **Note:** Version bump only for package @spinnaker/cloudfoundry diff --git a/packages/cloudfoundry/package.json b/packages/cloudfoundry/package.json index 4cbddfe5cc9..5a7a6861095 100644 --- a/packages/cloudfoundry/package.json +++ b/packages/cloudfoundry/package.json @@ -1,9 +1,12 @@ { "name": "@spinnaker/cloudfoundry", "license": "Apache-2.0", - "version": "0.1.7", + "version": "0.1.11", "module": "dist/index.js", "typings": "dist/index.d.ts", + "publishConfig": { + "access": "public" + }, "scripts": { "clean": "shx rm -rf dist", "prepublishOnly": "npm run build", @@ -13,7 +16,7 @@ "lib": "npm run build" }, "dependencies": { - "@spinnaker/core": "^0.24.1", + "@spinnaker/core": "^0.28.0", "@uirouter/react": "1.0.7", "@uirouter/react-hybrid": "1.0.2", "angular": "1.6.10", @@ -30,9 +33,9 @@ }, "devDependencies": { "@rollup/plugin-alias": "^3.1.2", - "@spinnaker/eslint-plugin": "^3.0.1", + "@spinnaker/eslint-plugin": "^3.0.2", "@spinnaker/mocks": "1.0.7", - "@spinnaker/scripts": "^0.3.0", + "@spinnaker/scripts": "^0.4.0", "@types/angular": "1.6.26", "@types/angular-ui-bootstrap": "0.13.41", "@types/enzyme": "3.10.3", diff --git a/packages/cloudrun/CHANGELOG.md b/packages/cloudrun/CHANGELOG.md index 1131f353b7a..9c91ab89070 100644 --- a/packages/cloudrun/CHANGELOG.md +++ b/packages/cloudrun/CHANGELOG.md @@ -3,6 +3,41 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.1.7](https://github.com/spinnaker/deck/compare/@spinnaker/cloudrun@0.1.6...@spinnaker/cloudrun@0.1.7) (2023-12-07) + +**Note:** Version bump only for package @spinnaker/cloudrun + + + + + +## [0.1.6](https://github.com/spinnaker/deck/compare/@spinnaker/cloudrun@0.1.5...@spinnaker/cloudrun@0.1.6) (2023-10-16) + + +### Bug Fixes + +* **publish:** set access config in deck libraries ([#10049](https://github.com/spinnaker/deck/issues/10049)) ([2a5ebe2](https://github.com/spinnaker/deck/commit/2a5ebe25662eeb9d41b5071749266bf9d6d51104)) + + + + + +## [0.1.5](https://github.com/spinnaker/deck/compare/@spinnaker/cloudrun@0.1.4...@spinnaker/cloudrun@0.1.5) (2023-09-06) + +**Note:** Version bump only for package @spinnaker/cloudrun + + + + + +## [0.1.4](https://github.com/spinnaker/deck/compare/@spinnaker/cloudrun@0.1.3...@spinnaker/cloudrun@0.1.4) (2023-07-20) + +**Note:** Version bump only for package @spinnaker/cloudrun + + + + + ## [0.1.3](https://github.com/spinnaker/deck/compare/@spinnaker/cloudrun@0.1.2...@spinnaker/cloudrun@0.1.3) (2023-06-02) **Note:** Version bump only for package @spinnaker/cloudrun diff --git a/packages/cloudrun/package.json b/packages/cloudrun/package.json index c74e16b64c4..2a3926df176 100644 --- a/packages/cloudrun/package.json +++ b/packages/cloudrun/package.json @@ -1,9 +1,12 @@ { "name": "@spinnaker/cloudrun", "license": "Apache-2.0", - "version": "0.1.3", + "version": "0.1.7", "module": "dist/index.js", "typings": "dist/index.d.ts", + "publishConfig": { + "access": "public" + }, "scripts": { "clean": "shx rm -rf dist", "prepublishOnly": "npm run build", @@ -13,7 +16,7 @@ "lib": "npm run build" }, "dependencies": { - "@spinnaker/core": "^0.24.1", + "@spinnaker/core": "^0.28.0", "@uirouter/angularjs": "1.0.26", "@uirouter/react": "1.0.7", "angular": "1.6.10", @@ -24,7 +27,7 @@ "formik": "1.5.1", "js-yaml": "3.13.1", "lodash": "4.17.21", - "luxon": "1.23.0", + "luxon": "1.28.1", "ngimport": "0.6.1", "react": "16.14.0", "react-ace": "6.4.0", @@ -35,9 +38,9 @@ "rxjs": "6.6.7" }, "devDependencies": { - "@spinnaker/eslint-plugin": "^3.0.1", - "@spinnaker/scripts": "^0.3.0", - "@types/angular": "1.6.26", + "@spinnaker/eslint-plugin": "^3.0.2", + "@spinnaker/scripts": "^0.4.0", + "@types/angular": "1.8.5", "@types/angular-ui-bootstrap": "0.13.41", "@types/dompurify": "^2.3.3", "@types/enzyme": "3.10.3", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 65c2c4b5d41..f312534f8e1 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -3,6 +3,78 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [0.28.0](https://github.com/spinnaker/deck/compare/@spinnaker/core@0.27.0...@spinnaker/core@0.28.0) (2023-12-07) + + +### Features + +* Add feature flag for multi block failure messages. ([#10061](https://github.com/spinnaker/deck/issues/10061)) ([374f724](https://github.com/spinnaker/deck/commit/374f724de221d68030a86e1f6452e3303390339a)) +* Expose spinnaker/kayenta to the plugin framework to allow us to create kayenta plugins in Deck ([#10072](https://github.com/spinnaker/deck/issues/10072)) ([dbf0574](https://github.com/spinnaker/deck/commit/dbf0574176cbbca781d970c64dfe49f6911ef8b8)) +* Split deployment failure messages. ([#10060](https://github.com/spinnaker/deck/issues/10060)) ([73dda48](https://github.com/spinnaker/deck/commit/73dda48caccd969ef562af3f86bc1f17efbdad7f)) + + + + + +# [0.27.0](https://github.com/spinnaker/deck/compare/@spinnaker/core@0.26.0...@spinnaker/core@0.27.0) (2023-10-16) + + +### Bug Fixes + +* **publish:** set access config in deck libraries ([#10049](https://github.com/spinnaker/deck/issues/10049)) ([2a5ebe2](https://github.com/spinnaker/deck/commit/2a5ebe25662eeb9d41b5071749266bf9d6d51104)) + + +### Features + +* **helm/bake:** Add additional input fields where we can fill in details of the APIs versions ([#10036](https://github.com/spinnaker/deck/issues/10036)) ([d968183](https://github.com/spinnaker/deck/commit/d9681830244ecd1c70cc02459f148d0822b7187e)) + + + + + +# [0.26.0](https://github.com/spinnaker/deck/compare/@spinnaker/core@0.25.0...@spinnaker/core@0.26.0) (2023-09-06) + + +### Features + +* **core:** Add ability to set Default Tag filters for an application in application config ([#10020](https://github.com/spinnaker/deck/issues/10020)) ([c768e88](https://github.com/spinnaker/deck/commit/c768e88fbc893d0bd5dc86959320a7b7d67443e5)) + + +### Reverts + +* Revert "fix(core): conditionally hide expression evaluation warning messages (#9771)" (#10021) ([62033d0](https://github.com/spinnaker/deck/commit/62033d0fc6f0a953bd3f01e4452664b92fd02dfb)), closes [#9771](https://github.com/spinnaker/deck/issues/9771) [#10021](https://github.com/spinnaker/deck/issues/10021) + + + +# 3.15.0 (2023-07-27) + + +### Features + +* **core:** set Cancellation Reason to be expanded by default ([#10018](https://github.com/spinnaker/deck/issues/10018)) ([db06e88](https://github.com/spinnaker/deck/commit/db06e88bada70fa4065f56fc33af7207943415c5)) + + + + + +# [0.25.0](https://github.com/spinnaker/deck/compare/@spinnaker/core@0.24.1...@spinnaker/core@0.25.0) (2023-07-20) + + +### Bug Fixes + +* **core/pipeline:** Resolved issue getting during pipeline save with spaces in pipeline name. ([#10009](https://github.com/spinnaker/deck/issues/10009)) ([ec8d2bb](https://github.com/spinnaker/deck/commit/ec8d2bbada0192673cfede4401e5c18d884dec59)) + + +### Features + +* **artifacts:** Add support for artifact store views and calls ([#10011](https://github.com/spinnaker/deck/issues/10011)) ([b520bae](https://github.com/spinnaker/deck/commit/b520bae8296c85ed096ea6aaee022e114bb6a52f)) +* **lambda:** Migrate Lambda plugin to OSS ([#9988](https://github.com/spinnaker/deck/issues/9988)) ([11f1cab](https://github.com/spinnaker/deck/commit/11f1cabb8efe8d7e034faf06ae3cb455eef6369a)), closes [#9984](https://github.com/spinnaker/deck/issues/9984) +* **stages/bakeManifests:** add helmfile support ([#9998](https://github.com/spinnaker/deck/issues/9998)) ([a4a0f33](https://github.com/spinnaker/deck/commit/a4a0f331d181b74d7c3a8c1b46724757be17a9f0)) + + + + + ## [0.24.1](https://github.com/spinnaker/deck/compare/@spinnaker/core@0.24.0...@spinnaker/core@0.24.1) (2023-06-02) diff --git a/packages/core/package.json b/packages/core/package.json index 111e652e230..2bb7eb46ef6 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,9 +1,12 @@ { "name": "@spinnaker/core", "license": "Apache-2.0", - "version": "0.24.1", + "version": "0.28.0", "module": "dist/index.js", "typings": "dist/index.d.ts", + "publishConfig": { + "access": "public" + }, "scripts": { "clean": "shx rm -rf dist", "prepublishOnly": "npm run build", @@ -17,7 +20,7 @@ "@apollo/client": "^3.6.9", "@fortawesome/fontawesome-free": "5.5.0", "@spinnaker/mocks": "1.0.7", - "@spinnaker/presentation": "^0.3.0", + "@spinnaker/presentation": "^0.3.1", "@spinnaker/styleguide": "2.0.0", "@uirouter/angularjs": "1.0.26", "@uirouter/core": "6.0.8", @@ -88,8 +91,8 @@ "@graphql-codegen/typescript": "^1.22.4", "@graphql-codegen/typescript-operations": "^1.18.3", "@graphql-codegen/typescript-react-apollo": "^2.3.0", - "@spinnaker/eslint-plugin": "^3.0.1", - "@spinnaker/scripts": "^0.3.0", + "@spinnaker/eslint-plugin": "^3.0.2", + "@spinnaker/scripts": "^0.4.0", "@types/angular": "1.6.26", "@types/angular-mocks": "1.5.10", "@types/angular-ui-bootstrap": "0.13.41", diff --git a/packages/core/src/application/config/applicationConfig.controller.js b/packages/core/src/application/config/applicationConfig.controller.js index 14fb1cc0bde..4c9d3c2edb2 100644 --- a/packages/core/src/application/config/applicationConfig.controller.js +++ b/packages/core/src/application/config/applicationConfig.controller.js @@ -8,6 +8,7 @@ import { CORE_APPLICATION_CONFIG_APPLICATIONSNAPSHOTSECTION_COMPONENT } from './ import { CHAOS_MONKEY_CONFIG_COMPONENT } from '../../chaosMonkey/chaosMonkeyConfig.component'; import { SETTINGS } from '../../config/settings'; import { APPLICATION_DATA_SOURCE_EDITOR } from './dataSources/applicationDataSourceEditor.component'; +import { DEFAULT_TAG_FILTER_CONFIG } from './defaultTagFilter/defaultTagFilterConfig.component'; import { DELETE_APPLICATION_SECTION } from './deleteApplicationSection.module'; import { CORE_APPLICATION_CONFIG_LINKS_APPLICATIONLINKS_COMPONENT } from './links/applicationLinks.component'; import { ApplicationWriter } from '../service/ApplicationWriter'; @@ -25,6 +26,7 @@ module(CORE_APPLICATION_CONFIG_APPLICATIONCONFIG_CONTROLLER, [ CHAOS_MONKEY_CONFIG_COMPONENT, TRAFFIC_GUARD_CONFIG_COMPONENT, CORE_APPLICATION_CONFIG_LINKS_APPLICATIONLINKS_COMPONENT, + DEFAULT_TAG_FILTER_CONFIG, ]).controller('ApplicationConfigController', [ '$state', 'app', @@ -63,6 +65,30 @@ module(CORE_APPLICATION_CONFIG_APPLICATIONCONFIG_CONTROLLER, [ }); }; + this.defaultTagFilterProps = { + isSaving: false, + saveError: false, + }; + this.updateDefaultTagFilterConfigs = (tagConfigs /* IDefaultTagFilterConfig[] */) => { + const applicationAttributes = cloneDeep(this.application.attributes); + applicationAttributes.defaultFilteredTags = tagConfigs; + $scope.$applyAsync(() => { + this.defaultTagFilterProps.isSaving = true; + this.defaultTagFilterProps.saveError = false; + }); + ApplicationWriter.updateApplication(applicationAttributes) + .then(() => { + $scope.$applyAsync(() => { + this.defaultTagFilterProps.isSaving = false; + this.application.attributes = applicationAttributes; + }); + }) + .catch(() => { + this.defaultTagFilterProps.isSaving = false; + this.defaultTagFilterProps.saveError = true; + }); + }; + this.notifications = []; this.updateNotifications = (notifications) => { $scope.$applyAsync(() => { diff --git a/packages/core/src/application/config/applicationConfig.view.html b/packages/core/src/application/config/applicationConfig.view.html index 8e268432508..ca2912ce2e7 100644 --- a/packages/core/src/application/config/applicationConfig.view.html +++ b/packages/core/src/application/config/applicationConfig.view.html @@ -54,6 +54,15 @@ > + + + + diff --git a/packages/core/src/application/config/defaultTagFilter/DefaultTagFilterConfig.spec.tsx b/packages/core/src/application/config/defaultTagFilter/DefaultTagFilterConfig.spec.tsx new file mode 100644 index 00000000000..981d2affea6 --- /dev/null +++ b/packages/core/src/application/config/defaultTagFilter/DefaultTagFilterConfig.spec.tsx @@ -0,0 +1,75 @@ +import { shallow } from 'enzyme'; +import React from 'react'; + +import type { IDefaultTagFilterConfig } from './DefaultTagFilterConfig'; +import { DefaultTagFilterConfig } from './DefaultTagFilterConfig'; +import { noop } from '../../../utils'; + +describe('', () => { + let tagConfigs: IDefaultTagFilterConfig[]; + let wrapper: any; + + beforeEach(() => { + tagConfigs = getTestDefaultFilterTagConfigs(); + wrapper = shallow( + , + ); + }); + + describe('view', () => { + it('renders a row for each banner config', () => { + expect(wrapper.find('.default-filter-config-row').length).toEqual(tagConfigs.length); + }); + it('renders an "add" button', () => { + expect(wrapper.find('.add-new').length).toEqual(1); + }); + }); + + describe('functionality', () => { + it('update default tag filter config', () => { + expect(wrapper.state('defaultTagFilterConfigsEditing')).toEqual(tagConfigs); + wrapper + .find('textarea') + .at(1) + .simulate('change', { target: { value: 'hello' } }); + const updatedConfigs = [ + { + ...tagConfigs[0], + tagValue: 'hello', + }, + { + ...tagConfigs[1], + }, + ]; + expect(wrapper.state('defaultTagFilterConfigsEditing')).toEqual(updatedConfigs); + }); + it('add default filter tag config', () => { + expect(wrapper.state('defaultTagFilterConfigsEditing').length).toEqual(2); + wrapper.find('.add-new').simulate('click'); + expect(wrapper.state('defaultTagFilterConfigsEditing').length).toEqual(3); + }); + it('remove default filter tag config', () => { + expect(wrapper.state('defaultTagFilterConfigsEditing').length).toEqual(2); + wrapper.find('.default-filter-config-remove').at(1).simulate('click'); + expect(wrapper.state('defaultTagFilterConfigsEditing').length).toEqual(1); + }); + }); +}); + +export function getTestDefaultFilterTagConfigs(): IDefaultTagFilterConfig[] { + return [ + { + tagName: 'Pipeline Type', + tagValue: 'Deployment Pipelines', + }, + { + tagName: 'Pipeline Type', + tagValue: 'Repair Pipelines', + }, + ]; +} diff --git a/packages/core/src/application/config/defaultTagFilter/DefaultTagFilterConfig.tsx b/packages/core/src/application/config/defaultTagFilter/DefaultTagFilterConfig.tsx new file mode 100644 index 00000000000..8984ab50427 --- /dev/null +++ b/packages/core/src/application/config/defaultTagFilter/DefaultTagFilterConfig.tsx @@ -0,0 +1,161 @@ +import { isEqual } from 'lodash'; +import React from 'react'; + +import { ConfigSectionFooter } from '../footer/ConfigSectionFooter'; +import { noop } from '../../../utils'; + +import './defaultTagFilterConfig.less'; + +export interface IDefaultTagFilterConfig { + tagName: string; + tagValue: string; +} + +export interface IDefaultTagFilterProps { + defaultTagFilterConfigs: IDefaultTagFilterConfig[]; + isSaving: boolean; + saveError: boolean; + updateDefaultTagFilterConfigs: (defaultTagFilterConfigs: IDefaultTagFilterConfig[]) => void; +} + +export interface IDefaultTagFilterState { + defaultTagFilterConfigsEditing: IDefaultTagFilterConfig[]; +} + +export class DefaultTagFilterConfig extends React.Component { + public static defaultProps: Partial = { + defaultTagFilterConfigs: [], + isSaving: false, + saveError: false, + updateDefaultTagFilterConfigs: noop, + }; + + constructor(props: IDefaultTagFilterProps) { + super(props); + this.state = { + defaultTagFilterConfigsEditing: props.defaultTagFilterConfigs, + }; + } + + private onTagNameChange = (idx: number, text: string) => { + this.setState({ + defaultTagFilterConfigsEditing: this.state.defaultTagFilterConfigsEditing.map((config, i) => { + if (i === idx) { + return { + ...config, + tagName: text, + }; + } + return config; + }), + }); + }; + + private onTagValueChange = (idx: number, text: string) => { + this.setState({ + defaultTagFilterConfigsEditing: this.state.defaultTagFilterConfigsEditing.map((config, i) => { + if (i === idx) { + return { + ...config, + tagValue: text, + }; + } + return config; + }), + }); + }; + + private addFilterTag = (): void => { + this.setState({ + defaultTagFilterConfigsEditing: this.state.defaultTagFilterConfigsEditing.concat([ + { + tagName: 'Name of the tag (E.g. Pipeline Type)', + tagValue: 'Value of the tag (E.g. Default Pipelines)', + } as IDefaultTagFilterConfig, + ]), + }); + }; + + private removeFilterTag = (idx: number): void => { + this.setState({ + defaultTagFilterConfigsEditing: this.state.defaultTagFilterConfigsEditing.filter((_config, i) => i !== idx), + }); + }; + + private isDirty = (): boolean => { + return !isEqual(this.props.defaultTagFilterConfigs, this.state.defaultTagFilterConfigsEditing); + }; + + private onRevertClicked = (): void => { + this.setState({ + defaultTagFilterConfigsEditing: this.props.defaultTagFilterConfigs, + }); + }; + + private onSaveClicked = (): void => { + this.props.updateDefaultTagFilterConfigs(this.state.defaultTagFilterConfigsEditing); + }; + + public render() { + return ( +
+
+ Default Tag filters allow you to specify which tags are immediately filtered to when the pipeline execution + page is loaded in. +
+
+ + + + + + + + + {this.state.defaultTagFilterConfigsEditing.map((defaultTagFilter, idx) => ( + +
Tag NameTag Value
+