From 76ec58a0843e6f2246cb2947d31341d9ec810581 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 14 Jun 2019 12:29:39 +0100 Subject: [PATCH 01/29] Add primary keys to tables to support PXC --- .gitignore | 3 +++ .../datastore/20190614121900_PrimaryKeys.go | 26 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 src/jetstream/datastore/20190614121900_PrimaryKeys.go diff --git a/.gitignore b/.gitignore index ada922f429..a80266b29f 100644 --- a/.gitignore +++ b/.gitignore @@ -77,6 +77,9 @@ deploy/stratos-ui-release/dev_releases/ output/fissile deploy/ci/travis/temp/ +# Helm Chart Image List +deploy/kubernetes/console/imagelist.txt + .dist/ deploy/uaa/tmp/ src/backend/*/vendor/ diff --git a/src/jetstream/datastore/20190614121900_PrimaryKeys.go b/src/jetstream/datastore/20190614121900_PrimaryKeys.go new file mode 100644 index 0000000000..a1439ac90a --- /dev/null +++ b/src/jetstream/datastore/20190614121900_PrimaryKeys.go @@ -0,0 +1,26 @@ +package datastore + +import ( + "database/sql" + + "bitbucket.org/liamstask/goose/lib/goose" +) + +func init() { + RegisterMigration(20190614121900, "PrimaryKeys", func(txn *sql.Tx, conf *goose.DBConf) error { + + addTokensPrimaryKey := "ALTER TABLE tokens ADD CONSTRAINT PK_Tokens PRIMARY KEY (user_guid, cnsi_guid, token_guid)" + _, err := txn.Exec(addTokensPrimaryKey) + if err != nil { + return err + } + + addSetupConfigPrimaryKey := "ALTER TABLE console_config ADD CONSTRAINT PK_ConsoleConfig PRIMARY KEY (uaa_endpoint, console_admin_scope, console_client, console_client_secret, skip_ssl_validation)" + _, err = txn.Exec(addSetupConfigPrimaryKey) + if err != nil { + return err + } + + return nil + }) +} From 5e02b046728600bf6330a86c78aac624588ff1cc Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 15 Aug 2019 17:54:10 +0100 Subject: [PATCH 02/29] FIx: Sqlite dos not allow tables to be altered --- src/jetstream/datastore/20190614121900_PrimaryKeys.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/jetstream/datastore/20190614121900_PrimaryKeys.go b/src/jetstream/datastore/20190614121900_PrimaryKeys.go index a1439ac90a..5d25f9a96f 100644 --- a/src/jetstream/datastore/20190614121900_PrimaryKeys.go +++ b/src/jetstream/datastore/20190614121900_PrimaryKeys.go @@ -2,6 +2,7 @@ package datastore import ( "database/sql" + "strings" "bitbucket.org/liamstask/goose/lib/goose" ) @@ -9,13 +10,18 @@ import ( func init() { RegisterMigration(20190614121900, "PrimaryKeys", func(txn *sql.Tx, conf *goose.DBConf) error { - addTokensPrimaryKey := "ALTER TABLE tokens ADD CONSTRAINT PK_Tokens PRIMARY KEY (user_guid, cnsi_guid, token_guid)" + // Note: SQLite does not allow constraints to be added after table creation + if strings.Contains(conf.Driver.Name, "sqlite3") { + return nil + } + + addTokensPrimaryKey := "ALTER TABLE tokens ADD CONSTRAINT PK_Tokens PRIMARY KEY (user_guid, cnsi_guid, token_guid);" _, err := txn.Exec(addTokensPrimaryKey) if err != nil { return err } - addSetupConfigPrimaryKey := "ALTER TABLE console_config ADD CONSTRAINT PK_ConsoleConfig PRIMARY KEY (uaa_endpoint, console_admin_scope, console_client, console_client_secret, skip_ssl_validation)" + addSetupConfigPrimaryKey := "ALTER TABLE console_config ADD CONSTRAINT PK_ConsoleConfig PRIMARY KEY (uaa_endpoint, console_admin_scope, console_client, console_client_secret, skip_ssl_validation);" _, err = txn.Exec(addSetupConfigPrimaryKey) if err != nil { return err From 1f602b9788ba10234e7c07f9d7632b0b4e05d3d7 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Sun, 29 Sep 2019 11:25:37 +0100 Subject: [PATCH 03/29] Use smaller set of columns on primary key for console_config (there is only ever one row anyway) --- src/jetstream/datastore/20190614121900_PrimaryKeys.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jetstream/datastore/20190614121900_PrimaryKeys.go b/src/jetstream/datastore/20190614121900_PrimaryKeys.go index 5d25f9a96f..93520bbefe 100644 --- a/src/jetstream/datastore/20190614121900_PrimaryKeys.go +++ b/src/jetstream/datastore/20190614121900_PrimaryKeys.go @@ -21,7 +21,7 @@ func init() { return err } - addSetupConfigPrimaryKey := "ALTER TABLE console_config ADD CONSTRAINT PK_ConsoleConfig PRIMARY KEY (uaa_endpoint, console_admin_scope, console_client, console_client_secret, skip_ssl_validation);" + addSetupConfigPrimaryKey := "ALTER TABLE console_config ADD CONSTRAINT PK_ConsoleConfig PRIMARY KEY (uaa_endpoint, console_admin_scope);" _, err = txn.Exec(addSetupConfigPrimaryKey) if err != nil { return err From 99d4062c6ee0a4f19f4f435be47ec7e4f705e1ff Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Tue, 8 Oct 2019 16:47:35 +0100 Subject: [PATCH 04/29] Update migration name to avoid clashes, add PK to config table --- ...21900_PrimaryKeys.go => 20191008121900_PrimaryKeys.go} | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) rename src/jetstream/datastore/{20190614121900_PrimaryKeys.go => 20191008121900_PrimaryKeys.go} (75%) diff --git a/src/jetstream/datastore/20190614121900_PrimaryKeys.go b/src/jetstream/datastore/20191008121900_PrimaryKeys.go similarity index 75% rename from src/jetstream/datastore/20190614121900_PrimaryKeys.go rename to src/jetstream/datastore/20191008121900_PrimaryKeys.go index 93520bbefe..7ad8ba8165 100644 --- a/src/jetstream/datastore/20190614121900_PrimaryKeys.go +++ b/src/jetstream/datastore/20191008121900_PrimaryKeys.go @@ -8,7 +8,7 @@ import ( ) func init() { - RegisterMigration(20190614121900, "PrimaryKeys", func(txn *sql.Tx, conf *goose.DBConf) error { + RegisterMigration(20191008121900, "PrimaryKeys", func(txn *sql.Tx, conf *goose.DBConf) error { // Note: SQLite does not allow constraints to be added after table creation if strings.Contains(conf.Driver.Name, "sqlite3") { @@ -27,6 +27,12 @@ func init() { return err } + addConfigPrimaryKey := "ALTER TABLE config ADD CONSTRAINT PK_Config PRIMARY KEY (groupName, name);" + _, err = txn.Exec(addConfigPrimaryKey) + if err != nil { + return err + } + return nil }) } From d572e5c021b16b2c00dbfa01fcc4bcbeec054be4 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Tue, 8 Oct 2019 17:03:47 +0100 Subject: [PATCH 05/29] Remove cnsi_guid from pk in tokens table (stratos tokens) --- src/jetstream/datastore/20191008121900_PrimaryKeys.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jetstream/datastore/20191008121900_PrimaryKeys.go b/src/jetstream/datastore/20191008121900_PrimaryKeys.go index 7ad8ba8165..421bebfbcf 100644 --- a/src/jetstream/datastore/20191008121900_PrimaryKeys.go +++ b/src/jetstream/datastore/20191008121900_PrimaryKeys.go @@ -15,7 +15,7 @@ func init() { return nil } - addTokensPrimaryKey := "ALTER TABLE tokens ADD CONSTRAINT PK_Tokens PRIMARY KEY (user_guid, cnsi_guid, token_guid);" + addTokensPrimaryKey := "ALTER TABLE tokens ADD CONSTRAINT PK_Tokens PRIMARY KEY (user_guid, token_guid);" _, err := txn.Exec(addTokensPrimaryKey) if err != nil { return err From 0bc7882566cb0e7767ddc392d7dbfad7d94782e3 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Tue, 15 Oct 2019 16:40:11 +0100 Subject: [PATCH 06/29] Trivial change to ensure CI runs after changing mergeto branch --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 34bc801789..0a84dc3c3b 100644 --- a/README.md +++ b/README.md @@ -74,3 +74,4 @@ Tested with Browserstack ## License The work done has been licensed under Apache License 2.0. The license file can be found [here](LICENSE). + From 9308faf01269711a171912a04362f4d89d359dfa Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 15 Oct 2019 18:33:56 +0100 Subject: [PATCH 07/29] Bump base images --- deploy/common-build.sh | 2 +- deploy/db/Dockerfile.mariadb | 5 + deploy/db/mariadb-entrypoint.sh | 246 ++++++++++++++---- .../Dockerfile.stratos-bk-base.tmpl | 14 +- .../Dockerfile.stratos-bk-build-base.tmpl | 3 +- .../Dockerfile.stratos-go-build-base.tmpl | 8 +- .../Dockerfile.stratos-mariadb-base.tmpl | 9 +- .../Dockerfile.stratos-nginx-base.tmpl | 6 +- .../Dockerfile.stratos-ruby-build-base.tmpl | 6 +- .../Dockerfile.stratos-ui-build-base.tmpl | 9 +- .../stratos-base-images/build-base-images.sh | 12 +- deploy/stratos-base-images/install-ruby.sh | 17 +- 12 files changed, 261 insertions(+), 76 deletions(-) diff --git a/deploy/common-build.sh b/deploy/common-build.sh index ee5c771c20..2e8906adf4 100644 --- a/deploy/common-build.sh +++ b/deploy/common-build.sh @@ -43,7 +43,7 @@ function buildAndPublishImage { # Proxy support # Remove intermediate containers after a successful build -BUILD_ARGS="--rm=true" +BUILD_ARGS="--rm=true --squash" RUN_ARGS="" if [ -n "${http_proxy:-}" -o -n "${HTTP_PROXY:-}" ]; then BUILD_ARGS="${BUILD_ARGS} --build-arg http_proxy=${http_proxy:-${HTTP_PROXY}}" diff --git a/deploy/db/Dockerfile.mariadb b/deploy/db/Dockerfile.mariadb index 9bcb558457..c7abf8ea86 100644 --- a/deploy/db/Dockerfile.mariadb +++ b/deploy/db/Dockerfile.mariadb @@ -1,5 +1,10 @@ FROM splatform/stratos-db-base:opensuse +RUN \ + find /etc/ -name 'my*.cnf' -print0 \ + | xargs -0 grep -lZE '^(bind-address|log)' \ + | xargs -rt -0 sed -Ei 's/^(bind-address|log)/#&/'; + COPY mariadb-entrypoint.sh /docker-entrypoint.sh # ENTRYPOINT diff --git a/deploy/db/mariadb-entrypoint.sh b/deploy/db/mariadb-entrypoint.sh index 45e0471437..329d73569d 100755 --- a/deploy/db/mariadb-entrypoint.sh +++ b/deploy/db/mariadb-entrypoint.sh @@ -1,49 +1,205 @@ #!/bin/bash -set -e - -MYSQL_DATADIR="/var/lib/mysql" - -if [ ! -d "$MYSQL_DATADIR/mysql" ]; then - # if [ -z "$MYSQL_ROOT_PASSWORD" -a -z "$MYSQL_ALLOW_EMPTY_PASSWORD" ]; then - # echo >&2 'error: database is uninitialized and MYSQL_ROOT_PASSWORD not set' - # echo >&2 ' Did you forget to add -e MYSQL_ROOT_PASSWORD=... ?' - # exit 1 - # fi - - echo 'Running mysql_install_db ...' - mysql_install_db --user=mysql --datadir="$MYSQL_DATADIR" - echo 'Finished mysql_install_db' - - # These statements _must_ be on individual lines, and _must_ end with - # semicolons (no line breaks or comments are permitted). - # TODO proper SQL escaping on ALL the things D: - - tempSqlFile='/tmp/mysql-first-time.sql' - cat > "$tempSqlFile" <<-EOSQL -DELETE FROM mysql.user ; -CREATE USER 'root'@'%' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ; -GRANT ALL ON *.* TO 'root'@'%' WITH GRANT OPTION ; -DROP DATABASE IF EXISTS test ; -EOSQL - - if [ "$MYSQL_DATABASE" ]; then - echo "CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` ;" >> "$tempSqlFile" - fi - - if [ "$MYSQL_USER" -a "$MYSQL_PASSWORD" ]; then - echo "CREATE USER '$MYSQL_USER'@'%' IDENTIFIED BY '$MYSQL_PASSWORD' ;" >> "$tempSqlFile" - - if [ "$MYSQL_DATABASE" ]; then - echo "GRANT ALL ON \`$MYSQL_DATABASE\`.* TO '$MYSQL_USER'@'%' ;" >> "$tempSqlFile" - fi - fi - - echo 'FLUSH PRIVILEGES ;' >> "$tempSqlFile" - set -- "$@" --init-file="$tempSqlFile" +set -eo pipefail +shopt -s nullglob + +# if command starts with an option, prepend mysqld +if [ "${1:0:1}" = '-' ]; then + set -- mysqld "$@" +fi + +# skip setup if they want an option that stops mysqld +wantHelp= +for arg; do + case "$arg" in + -'?'|--help|--print-defaults|-V|--version) + wantHelp=1 + break + ;; + esac +done + +# usage: file_env VAR [DEFAULT] +# ie: file_env 'XYZ_DB_PASSWORD' 'example' +# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of +# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature) +file_env() { + local var="$1" + local fileVar="${var}_FILE" + local def="${2:-}" + if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then + echo >&2 "error: both $var and $fileVar are set (but are exclusive)" + exit 1 + fi + local val="$def" + if [ "${!var:-}" ]; then + val="${!var}" + elif [ "${!fileVar:-}" ]; then + val="$(< "${!fileVar}")" + fi + export "$var"="$val" + unset "$fileVar" +} + +_check_config() { + toRun=( "$@" --verbose --help --log-bin-index="$(mktemp -u)" ) + if ! errors="$("${toRun[@]}" 2>&1 >/dev/null)"; then + cat >&2 <<-EOM + + ERROR: mysqld failed while attempting to check config + command was: "${toRun[*]}" + + $errors + EOM + exit 1 + fi +} + +# Fetch value from server config +# We use mysqld --verbose --help instead of my_print_defaults because the +# latter only show values present in config files, and not server defaults +_get_config() { + local conf="$1"; shift + "$@" --verbose --help --log-bin-index="$(mktemp -u)" 2>/dev/null \ + | awk '$1 == "'"$conf"'" && /^[^ \t]/ { sub(/^[^ \t]+[ \t]+/, ""); print; exit }' + # match "datadir /some/path with/spaces in/it here" but not "--xyz=abc\n datadir (xyz)" +} + +# allow the container to be started with `--user` +if [ "$1" = 'mysqld' -a -z "$wantHelp" -a "$(id -u)" = '0' ]; then + _check_config "$@" + DATADIR="$(_get_config 'datadir' "$@")" + mkdir -p "$DATADIR" + find "$DATADIR" \! -user mysql -exec chown mysql '{}' + + exec gosu mysql "$BASH_SOURCE" "$@" fi -chown -R mysql:mysql "$MYSQL_DATADIR" -mkdir /var/run/mysql -chown -R mysql:mysql /var/run/mysql +if [ "$1" = 'mysqld' -a -z "$wantHelp" ]; then + # still need to check config, container may have started with --user + _check_config "$@" + # Get config + DATADIR="$(_get_config 'datadir' "$@")" + + echo "Data dir is: ${DATADIR}" + + if [ ! -d "$DATADIR/mysql" ]; then + file_env 'MYSQL_ROOT_PASSWORD' + if [ -z "$MYSQL_ROOT_PASSWORD" -a -z "$MYSQL_ALLOW_EMPTY_PASSWORD" -a -z "$MYSQL_RANDOM_ROOT_PASSWORD" ]; then + echo >&2 'error: database is uninitialized and password option is not specified ' + echo >&2 ' You need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD' + exit 1 + fi + + mkdir -p "$DATADIR" + # ======================================== + # Stratos changes + chown -R mysql:mysql "$DATADIR" + # ======================================== + + echo 'Initializing database' + installArgs=( --datadir="$DATADIR" --rpm ) + if { mysql_install_db --help || :; } | grep -q -- '--auth-root-authentication-method'; then + # beginning in 10.4.3, install_db uses "socket" which only allows system user root to connect, switch back to "normal" to allow mysql root without a password + # see https://github.com/MariaDB/server/commit/b9f3f06857ac6f9105dc65caae19782f09b47fb3 + # (this flag doesn't exist in 10.0 and below) + installArgs+=( --auth-root-authentication-method=normal ) + fi + # "Other options are passed to mysqld." (so we pass all "mysqld" arguments directly here) + mysql_install_db "${installArgs[@]}" "${@:2}" + echo 'Database initialized' + + SOCKET="$(_get_config 'socket' "$@")" + "$@" --skip-networking --socket="${SOCKET}" & + pid="$!" + + mysql=( mysql --protocol=socket -uroot -hlocalhost --socket="${SOCKET}" ) + + for i in {30..0}; do + if echo 'SELECT 1' | "${mysql[@]}" &> /dev/null; then + break + fi + echo 'MySQL init process in progress...' + sleep 1 + done + if [ "$i" = 0 ]; then + echo >&2 'MySQL init process failed.' + exit 1 + fi + + if [ -z "$MYSQL_INITDB_SKIP_TZINFO" ]; then + # sed is for https://bugs.mysql.com/bug.php?id=20545 + mysql_tzinfo_to_sql /usr/share/zoneinfo | sed 's/Local time zone must be set--see zic manual page/FCTY/' | "${mysql[@]}" mysql + fi + + if [ ! -z "$MYSQL_RANDOM_ROOT_PASSWORD" ]; then + export MYSQL_ROOT_PASSWORD="$(pwgen -1 32)" + echo "GENERATED ROOT PASSWORD: $MYSQL_ROOT_PASSWORD" + fi + + rootCreate= + # default root to listen for connections from anywhere + file_env 'MYSQL_ROOT_HOST' '%' + if [ ! -z "$MYSQL_ROOT_HOST" -a "$MYSQL_ROOT_HOST" != 'localhost' ]; then + # no, we don't care if read finds a terminating character in this heredoc + # https://unix.stackexchange.com/questions/265149/why-is-set-o-errexit-breaking-this-read-heredoc-expression/265151#265151 + read -r -d '' rootCreate <<-EOSQL || true + CREATE USER 'root'@'${MYSQL_ROOT_HOST}' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ; + GRANT ALL ON *.* TO 'root'@'${MYSQL_ROOT_HOST}' WITH GRANT OPTION ; + EOSQL + fi + + "${mysql[@]}" <<-EOSQL + -- What's done in this file shouldn't be replicated + -- or products like mysql-fabric won't work + SET @@SESSION.SQL_LOG_BIN=0; + + DELETE FROM mysql.user WHERE user NOT IN ('mysql.sys', 'mysqlxsys', 'root') OR host NOT IN ('localhost') ; + SET PASSWORD FOR 'root'@'localhost'=PASSWORD('${MYSQL_ROOT_PASSWORD}') ; + GRANT ALL ON *.* TO 'root'@'localhost' WITH GRANT OPTION ; + ${rootCreate} + DROP DATABASE IF EXISTS test ; + FLUSH PRIVILEGES ; + EOSQL + + if [ ! -z "$MYSQL_ROOT_PASSWORD" ]; then + mysql+=( -p"${MYSQL_ROOT_PASSWORD}" ) + fi + + file_env 'MYSQL_DATABASE' + if [ "$MYSQL_DATABASE" ]; then + echo "CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` ;" | "${mysql[@]}" + mysql+=( "$MYSQL_DATABASE" ) + fi + + file_env 'MYSQL_USER' + file_env 'MYSQL_PASSWORD' + if [ "$MYSQL_USER" -a "$MYSQL_PASSWORD" ]; then + echo "CREATE USER '$MYSQL_USER'@'%' IDENTIFIED BY '$MYSQL_PASSWORD' ;" | "${mysql[@]}" + + if [ "$MYSQL_DATABASE" ]; then + echo "GRANT ALL ON \`$MYSQL_DATABASE\`.* TO '$MYSQL_USER'@'%' ;" | "${mysql[@]}" + fi + fi + + echo + for f in /docker-entrypoint-initdb.d/*; do + case "$f" in + *.sh) echo "$0: running $f"; . "$f" ;; + *.sql) echo "$0: running $f"; "${mysql[@]}" < "$f"; echo ;; + *.sql.gz) echo "$0: running $f"; gunzip -c "$f" | "${mysql[@]}"; echo ;; + *) echo "$0: ignoring $f" ;; + esac + echo + done + + if ! kill -s TERM "$pid" || ! wait "$pid"; then + echo >&2 'MySQL init process failed.' + exit 1 + fi + + echo + echo 'MySQL init process done. Ready for start up.' + echo + fi +fi -exec "$@" +exec "$@" \ No newline at end of file diff --git a/deploy/stratos-base-images/Dockerfile.stratos-bk-base.tmpl b/deploy/stratos-base-images/Dockerfile.stratos-bk-base.tmpl index 712ce98087..b407999156 100644 --- a/deploy/stratos-base-images/Dockerfile.stratos-bk-base.tmpl +++ b/deploy/stratos-base-images/Dockerfile.stratos-bk-base.tmpl @@ -12,12 +12,20 @@ RUN zypper in -y ca-certificates && \ mkdir -p /root/.npm-global # Install latest git from devel/tools/scm repository {{^IS_SLE}} -RUN zypper ar http://download.opensuse.org/repositories/devel:/tools:/scm/openSUSE_Leap_42.3/devel:tools:scm.repo && \ - zypper --no-gpg-checks in -y git +RUN zypper ar http://download.opensuse.org/repositories/devel:/tools:/scm/openSUSE_Leap_15.1/devel:tools:scm.repo && \ + zypper --no-gpg-checks in -y git && \ + zypper rr devel_tools_scm && \ + zypper clean -a && \ + rm -f /var/log/zypper.log /var/log/zypp/history + {{/IS_SLE}} {{#IS_SLE}} RUN zypper ar http://download.opensuse.org/repositories/devel:/tools:/scm/SLE_12_SP3/devel:tools:scm.repo && \ - zypper --no-gpg-checks in -y git + zypper --no-gpg-checks in -y git && \ + zypper rr devel_tools_scm && \ + zypper clean -a && \ + rm -f /var/log/zypper.log /var/log/zypp/history + {{/IS_SLE}} {{#IS_SLE}} diff --git a/deploy/stratos-base-images/Dockerfile.stratos-bk-build-base.tmpl b/deploy/stratos-base-images/Dockerfile.stratos-bk-build-base.tmpl index 169657a541..d1769ba1e4 100644 --- a/deploy/stratos-base-images/Dockerfile.stratos-bk-build-base.tmpl +++ b/deploy/stratos-base-images/Dockerfile.stratos-bk-build-base.tmpl @@ -6,7 +6,8 @@ RUN useradd -ms /bin/bash stratos && \ chgrp -R users /home/stratos RUN cd / && wget https://nodejs.org/dist/v8.11.2/node-v8.11.2-linux-x64.tar.xz && \ - tar -xf node-v8.11.2-linux-x64.tar.xz + tar -xf node-v8.11.2-linux-x64.tar.xz && \ + rm node-v8.11.2-linux-x64.tar.xz ENV USER=stratos ENV PATH=$PATH:/node-v8.11.2-linux-x64/bin USER stratos diff --git a/deploy/stratos-base-images/Dockerfile.stratos-go-build-base.tmpl b/deploy/stratos-base-images/Dockerfile.stratos-go-build-base.tmpl index 49a0687acf..1b456dd78b 100644 --- a/deploy/stratos-base-images/Dockerfile.stratos-go-build-base.tmpl +++ b/deploy/stratos-base-images/Dockerfile.stratos-go-build-base.tmpl @@ -9,10 +9,14 @@ RUN zypper ref {{/IS_SLE}} RUN zypper -n ref && \ -zypper -n up && \ -zypper in -y which tar git gcc curl wget + zypper -n up && \ + zypper in -y which tar git gcc curl wget gzip xz && \ + zypper clean -a && \ + rm -f /var/log/zypper.log /var/log/zypp/history + RUN wget https://storage.googleapis.com/golang/go1.12.4.linux-amd64.tar.gz && \ tar -xzf go1.12.4.linux-amd64.tar.gz -C /usr/local/ && \ + rm go1.12.4.linux-amd64.tar.gz && \ mkdir -p /home/stratos/go/bin && \ mkdir -p /home/stratos/go/src diff --git a/deploy/stratos-base-images/Dockerfile.stratos-mariadb-base.tmpl b/deploy/stratos-base-images/Dockerfile.stratos-mariadb-base.tmpl index 61d6d758df..e8ee58f007 100644 --- a/deploy/stratos-base-images/Dockerfile.stratos-mariadb-base.tmpl +++ b/deploy/stratos-base-images/Dockerfile.stratos-mariadb-base.tmpl @@ -9,8 +9,13 @@ RUN zypper ref ENV MYSQL_ROOT_PASSWORD mysecretpassword # Add repo for the latest mariadb -RUN zypper in -y mariadb net-tools \ - && zypper clean --all +RUN zypper in -y mariadb net-tools mariadb-tools timezone wget && \ + zypper clean -a && \ + rm -f /var/log/zypper.log /var/log/zypp/history + +ARG GOSU_VERSION=1.11 +RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-amd64" \ + && chmod +x /usr/local/bin/gosu # Config for mariadb RUN rm -rf /var/lib/mysql \ diff --git a/deploy/stratos-base-images/Dockerfile.stratos-nginx-base.tmpl b/deploy/stratos-base-images/Dockerfile.stratos-nginx-base.tmpl index 021c2d9ff3..d7385bd150 100644 --- a/deploy/stratos-base-images/Dockerfile.stratos-nginx-base.tmpl +++ b/deploy/stratos-base-images/Dockerfile.stratos-nginx-base.tmpl @@ -5,8 +5,10 @@ RUN zypper addrepo -t rpm-md -G -c '{{SMT_INTERNAL_SERVER}}' smt_internal_server {{/IS_SLE}} RUN zypper -n ref && \ zypper -n up && \ - zypper in -y nginx apache2-utils + zypper in -y nginx apache2-utils && \ + zypper clean -a && \ + rm -f /var/log/zypper.log /var/log/zypp/history {{#IS_SLE}} RUN zypper rr smt_internal_server -{{/IS_SLE}} \ No newline at end of file +{{/IS_SLE}} \ No newline at end of file diff --git a/deploy/stratos-base-images/Dockerfile.stratos-ruby-build-base.tmpl b/deploy/stratos-base-images/Dockerfile.stratos-ruby-build-base.tmpl index 2a23f226a8..c94f37a01a 100644 --- a/deploy/stratos-base-images/Dockerfile.stratos-ruby-build-base.tmpl +++ b/deploy/stratos-base-images/Dockerfile.stratos-ruby-build-base.tmpl @@ -9,8 +9,10 @@ RUN zypper ref {{/IS_SLE}} RUN zypper -n ref && \ -zypper -n up && \ -zypper in -y which tar curl wget + zypper -n up && \ + zypper in -y which tar curl wget gzip && \ + zypper clean -a && \ + rm -f /var/log/zypper.log /var/log/zypp/history {{#IS_SLE}} RUN zypper rr smt_internal diff --git a/deploy/stratos-base-images/Dockerfile.stratos-ui-build-base.tmpl b/deploy/stratos-base-images/Dockerfile.stratos-ui-build-base.tmpl index db0af5208b..1ee3d66e2b 100644 --- a/deploy/stratos-base-images/Dockerfile.stratos-ui-build-base.tmpl +++ b/deploy/stratos-base-images/Dockerfile.stratos-ui-build-base.tmpl @@ -9,8 +9,11 @@ RUN zypper ref {{/IS_SLE}} RUN zypper -n ref && \ -zypper -n up && \ -zypper in -y wget tar git + zypper -n up && \ + zypper in -y wget tar git xz && \ + zypper clean -a && \ + rm -f /var/log/zypper.log /var/log/zypp/history + RUN cd / && wget https://nodejs.org/dist/v8.11.2/node-v8.11.2-linux-x64.tar.xz && \ tar -xf node-v8.11.2-linux-x64.tar.xz ENV USER=stratos @@ -30,5 +33,5 @@ RUN zypper rr smt_internal_sdk RUN zypper rr smt_internal_server {{/IS_SLE}} -USER stratos +USER stratos WORKDIR /home/stratos \ No newline at end of file diff --git a/deploy/stratos-base-images/build-base-images.sh b/deploy/stratos-base-images/build-base-images.sh index e85a2f6061..f29006238e 100755 --- a/deploy/stratos-base-images/build-base-images.sh +++ b/deploy/stratos-base-images/build-base-images.sh @@ -7,11 +7,13 @@ YELLOW="\033[93m" RESET="\033[0m" BOLD="\033[1m" -BASE_IMAGE=opensuse:42.3 +BASE_IMAGE=opensuse/leap:15.1 REGISTRY=docker.io ORGANIZATION=splatform TAG=opensuse PROG=$(basename ${BASH_SOURCE[0]}) +SQUASH_ARGS="--squash" +NO_SQUASH="stratos-base" function usage { echo "usage: $PROG [-b BASE] [-r REGISTRY] [-o ORGANIZATION] [-t TAG] [-p] [h]" @@ -115,8 +117,14 @@ build_and_push_image() { printf "${CYAN}Building image ${YELLOW}${image_name}${CYAN} with docker file ${YELLOW}${docker_file}${RESET}\n" printf "${CYAN}========= >>>>${RESET}\n" echo "" + + # We can't squash base image as its the same as the base (just re-tagged) + ARG="" + if [ "${image_name}" != "$NO_SQUASH" ]; then + ARG="${SQUASH_ARGS}" + fi set -x - docker build . -f $docker_file -t ${REGISTRY}/${ORGANIZATION}/${image_name}:${TAG} + docker build ${ARG} . -f $docker_file -t ${REGISTRY}/${ORGANIZATION}/${image_name}:${TAG} if [ ! -z ${PUSH_IMAGES} ]; then docker push ${REGISTRY}/${ORGANIZATION}/${image_name}:${TAG} fi diff --git a/deploy/stratos-base-images/install-ruby.sh b/deploy/stratos-base-images/install-ruby.sh index fc8a4bd622..86d0f572c0 100755 --- a/deploy/stratos-base-images/install-ruby.sh +++ b/deploy/stratos-base-images/install-ruby.sh @@ -15,20 +15,11 @@ set -e if [ "$IS_SLES" == "false" ]; then zypper in -y curl jq make gcc-c++ - zypper in -y libopenssl-devel readline-devel + zypper in -y libopenssl-devel readline-devel + zypper in -y ruby-devel fi -# Build from source -mkdir -p /tmp/ruby -cd /tmp/ruby -wget https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.8.tar.gz -gunzip ./ruby-2.3.8.tar.gz -tar -xvf ./ruby-2.3.8.tar -cd ruby-2.3.8 - -./configure - -make -make install +# OpenSUSE Leap 15.1 will install ruby 2.5 +zypper in ruby ruby --version From 5be2b484c23c6b49cf3a76c02a04cb28c0415f71 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 16 Oct 2019 09:50:07 +0100 Subject: [PATCH 08/29] Fixes for SLES15 base images --- .../Dockerfile.stratos-base.tmpl | 4 ++++ .../Dockerfile.stratos-bk-base.tmpl | 11 ++++++----- .../Dockerfile.stratos-go-build-base.tmpl | 9 +++++---- .../Dockerfile.stratos-mariadb-base.tmpl | 9 ++++++++- .../Dockerfile.stratos-nginx-base.tmpl | 11 ++++++++++- .../Dockerfile.stratos-ruby-build-base.tmpl | 15 ++++++++------- .../Dockerfile.stratos-ui-build-base.tmpl | 9 +++++---- deploy/stratos-base-images/install-ruby.sh | 2 +- 8 files changed, 47 insertions(+), 23 deletions(-) diff --git a/deploy/stratos-base-images/Dockerfile.stratos-base.tmpl b/deploy/stratos-base-images/Dockerfile.stratos-base.tmpl index 701a2fbde5..b956a582b7 100644 --- a/deploy/stratos-base-images/Dockerfile.stratos-base.tmpl +++ b/deploy/stratos-base-images/Dockerfile.stratos-base.tmpl @@ -1,3 +1,7 @@ FROM {{BASE_IMAGE}} +{{#IS_SLE}} +RUN rm -f /usr/lib/zypp/plugins/services/container-suseconnect-zypp +{{/IS_SLE}} + WORKDIR /srv diff --git a/deploy/stratos-base-images/Dockerfile.stratos-bk-base.tmpl b/deploy/stratos-base-images/Dockerfile.stratos-bk-base.tmpl index b407999156..52089892b3 100644 --- a/deploy/stratos-base-images/Dockerfile.stratos-bk-base.tmpl +++ b/deploy/stratos-base-images/Dockerfile.stratos-bk-base.tmpl @@ -1,6 +1,7 @@ FROM {{BASE_IMAGE}} {{#IS_SLE}} +RUN rm -f /usr/lib/zypp/plugins/services/container-suseconnect-zypp RUN zypper addrepo -G -c '{{SMT_INTERNAL}}' smt_internal RUN zypper addrepo -t rpm-md -G -c '{{SMT_INTERNAL_UPDATE}}' smt_internal_update RUN zypper addrepo -t rpm-md -G -c '{{SMT_INTERNAL_SDK}}' smt_internal_sdk @@ -20,7 +21,7 @@ RUN zypper ar http://download.opensuse.org/repositories/devel:/tools:/scm/openSU {{/IS_SLE}} {{#IS_SLE}} -RUN zypper ar http://download.opensuse.org/repositories/devel:/tools:/scm/SLE_12_SP3/devel:tools:scm.repo && \ +RUN zypper ar http://download.opensuse.org/repositories/devel:/tools:/scm/SLE_15/devel:tools:scm.repo && \ zypper --no-gpg-checks in -y git && \ zypper rr devel_tools_scm && \ zypper clean -a && \ @@ -29,9 +30,9 @@ RUN zypper ar http://download.opensuse.org/repositories/devel:/tools:/scm/SLE_12 {{/IS_SLE}} {{#IS_SLE}} -RUN zypper rr smt_internal -RUN zypper rr smt_internal_update -RUN zypper rr smt_internal_sdk -RUN zypper rr smt_internal_server +RUN zypper rr smt_internal +RUN zypper rr smt_internal_update +RUN zypper rr smt_internal_sdk +RUN zypper rr smt_internal_server {{/IS_SLE}} WORKDIR /srv diff --git a/deploy/stratos-base-images/Dockerfile.stratos-go-build-base.tmpl b/deploy/stratos-base-images/Dockerfile.stratos-go-build-base.tmpl index 1b456dd78b..8bf64ec325 100644 --- a/deploy/stratos-base-images/Dockerfile.stratos-go-build-base.tmpl +++ b/deploy/stratos-base-images/Dockerfile.stratos-go-build-base.tmpl @@ -1,6 +1,7 @@ FROM {{BASE_IMAGE}} {{#IS_SLE}} +RUN rm -f /usr/lib/zypp/plugins/services/container-suseconnect-zypp RUN zypper addrepo -G -c '{{SMT_INTERNAL}}' smt_internal RUN zypper addrepo -t rpm-md -G -c '{{SMT_INTERNAL_UPDATE}}' smt_internal_update RUN zypper addrepo -t rpm-md -G -c '{{SMT_INTERNAL_SDK}}' smt_internal_sdk @@ -32,10 +33,10 @@ RUN curl https://glide.sh/get | sh RUN curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh {{#IS_SLE}} -RUN zypper rr smt_internal -RUN zypper rr smt_internal_update -RUN zypper rr smt_internal_sdk -RUN zypper rr smt_internal_server +RUN zypper rr smt_internal +RUN zypper rr smt_internal_update +RUN zypper rr smt_internal_sdk +RUN zypper rr smt_internal_server {{/IS_SLE}} WORKDIR /home/stratos/go diff --git a/deploy/stratos-base-images/Dockerfile.stratos-mariadb-base.tmpl b/deploy/stratos-base-images/Dockerfile.stratos-mariadb-base.tmpl index e8ee58f007..428811512b 100644 --- a/deploy/stratos-base-images/Dockerfile.stratos-mariadb-base.tmpl +++ b/deploy/stratos-base-images/Dockerfile.stratos-mariadb-base.tmpl @@ -1,7 +1,11 @@ FROM {{BASE_IMAGE}} {{#IS_SLE}} +RUN rm -f /usr/lib/zypp/plugins/services/container-suseconnect-zypp RUN zypper addrepo -G -c '{{SMT_INTERNAL}}' smt_internal +RUN zypper addrepo -t rpm-md -G -c '{{SMT_INTERNAL_UPDATE}}' smt_internal_update +RUN zypper addrepo -t rpm-md -G -c '{{SMT_INTERNAL_SDK}}' smt_internal_sdk +RUN zypper addrepo -t rpm-md -G -c '{{SMT_INTERNAL_SERVER}}' smt_internal_server RUN zypper ref {{/IS_SLE}} @@ -25,7 +29,10 @@ RUN rm -rf /var/lib/mysql \ && chown -R mysql:mysql /var/log/mysql {{#IS_SLE}} -RUN zypper rr smt_internal +RUN zypper rr smt_internal +RUN zypper rr smt_internal_update +RUN zypper rr smt_internal_sdk +RUN zypper rr smt_internal_server {{/IS_SLE}} VOLUME ["/var/lib/mysql"] \ No newline at end of file diff --git a/deploy/stratos-base-images/Dockerfile.stratos-nginx-base.tmpl b/deploy/stratos-base-images/Dockerfile.stratos-nginx-base.tmpl index d7385bd150..d42d542d5b 100644 --- a/deploy/stratos-base-images/Dockerfile.stratos-nginx-base.tmpl +++ b/deploy/stratos-base-images/Dockerfile.stratos-nginx-base.tmpl @@ -1,8 +1,13 @@ FROM {{BASE_IMAGE}} {{#IS_SLE}} -RUN zypper addrepo -G -t yum -c 'http://nginx.org/packages/sles/12' nginx +RUN rm -f /usr/lib/zypp/plugins/services/container-suseconnect-zypp +RUN zypper addrepo -G -t yum -c 'http://nginx.org/packages/sles/15' nginx +RUN zypper addrepo -G -c '{{SMT_INTERNAL}}' smt_internal +RUN zypper addrepo -t rpm-md -G -c '{{SMT_INTERNAL_UPDATE}}' smt_internal_update +RUN zypper addrepo -t rpm-md -G -c '{{SMT_INTERNAL_SDK}}' smt_internal_sdk RUN zypper addrepo -t rpm-md -G -c '{{SMT_INTERNAL_SERVER}}' smt_internal_server {{/IS_SLE}} + RUN zypper -n ref && \ zypper -n up && \ zypper in -y nginx apache2-utils && \ @@ -10,5 +15,9 @@ RUN zypper -n ref && \ rm -f /var/log/zypper.log /var/log/zypp/history {{#IS_SLE}} +RUN zypper rr nginx +RUN zypper rr smt_internal +RUN zypper rr smt_internal_update +RUN zypper rr smt_internal_sdk RUN zypper rr smt_internal_server {{/IS_SLE}} \ No newline at end of file diff --git a/deploy/stratos-base-images/Dockerfile.stratos-ruby-build-base.tmpl b/deploy/stratos-base-images/Dockerfile.stratos-ruby-build-base.tmpl index c94f37a01a..fd1728474c 100644 --- a/deploy/stratos-base-images/Dockerfile.stratos-ruby-build-base.tmpl +++ b/deploy/stratos-base-images/Dockerfile.stratos-ruby-build-base.tmpl @@ -1,6 +1,7 @@ FROM {{BASE_IMAGE}} {{#IS_SLE}} +RUN rm -f /usr/lib/zypp/plugins/services/container-suseconnect-zypp RUN zypper addrepo -G -c '{{SMT_INTERNAL}}' smt_internal RUN zypper addrepo -t rpm-md -G -c '{{SMT_INTERNAL_UPDATE}}' smt_internal_update RUN zypper addrepo -t rpm-md -G -c '{{SMT_INTERNAL_SDK}}' smt_internal_sdk @@ -14,15 +15,15 @@ RUN zypper -n ref && \ zypper clean -a && \ rm -f /var/log/zypper.log /var/log/zypp/history -{{#IS_SLE}} -RUN zypper rr smt_internal -RUN zypper rr smt_internal_update -RUN zypper rr smt_internal_sdk -RUN zypper rr smt_internal_server -{{/IS_SLE}} - WORKDIR / USER root ADD install-ruby.sh /install-ruby.sh RUN /install-ruby.sh + +{{#IS_SLE}} +RUN zypper rr smt_internal +RUN zypper rr smt_internal_update +RUN zypper rr smt_internal_sdk +RUN zypper rr smt_internal_server +{{/IS_SLE}} diff --git a/deploy/stratos-base-images/Dockerfile.stratos-ui-build-base.tmpl b/deploy/stratos-base-images/Dockerfile.stratos-ui-build-base.tmpl index 1ee3d66e2b..7d528c8cc5 100644 --- a/deploy/stratos-base-images/Dockerfile.stratos-ui-build-base.tmpl +++ b/deploy/stratos-base-images/Dockerfile.stratos-ui-build-base.tmpl @@ -1,6 +1,7 @@ FROM {{BASE_IMAGE}} {{#IS_SLE}} +RUN rm -f /usr/lib/zypp/plugins/services/container-suseconnect-zypp RUN zypper addrepo -G -c '{{SMT_INTERNAL}}' smt_internal RUN zypper addrepo -t rpm-md -G -c '{{SMT_INTERNAL_UPDATE}}' smt_internal_update RUN zypper addrepo -t rpm-md -G -c '{{SMT_INTERNAL_SDK}}' smt_internal_sdk @@ -27,10 +28,10 @@ RUN useradd -ms /bin/bash stratos && \ chgrp users /usr/dist {{#IS_SLE}} -RUN zypper rr smt_internal -RUN zypper rr smt_internal_update -RUN zypper rr smt_internal_sdk -RUN zypper rr smt_internal_server +RUN zypper rr smt_internal +RUN zypper rr smt_internal_update +RUN zypper rr smt_internal_sdk +RUN zypper rr smt_internal_server {{/IS_SLE}} USER stratos diff --git a/deploy/stratos-base-images/install-ruby.sh b/deploy/stratos-base-images/install-ruby.sh index 86d0f572c0..237851a43a 100755 --- a/deploy/stratos-base-images/install-ruby.sh +++ b/deploy/stratos-base-images/install-ruby.sh @@ -20,6 +20,6 @@ if [ "$IS_SLES" == "false" ]; then fi # OpenSUSE Leap 15.1 will install ruby 2.5 -zypper in ruby +zypper in -y ruby ruby --version From 93fe78a7038d2f28bfd0dec18cf733f4b909c7ef Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 16 Oct 2019 15:26:17 +0100 Subject: [PATCH 09/29] Fixes for newer mariadb --- deploy/db/Dockerfile.mariadb | 1 + deploy/db/mariadb-ping.sh | 3 +++ deploy/kubernetes/console/templates/deployment.yaml | 10 ++++------ .../Dockerfile.stratos-bk-base.tmpl | 2 ++ .../Dockerfile.stratos-mariadb-base.tmpl | 2 +- 5 files changed, 11 insertions(+), 7 deletions(-) create mode 100755 deploy/db/mariadb-ping.sh diff --git a/deploy/db/Dockerfile.mariadb b/deploy/db/Dockerfile.mariadb index c7abf8ea86..16ccff17a2 100644 --- a/deploy/db/Dockerfile.mariadb +++ b/deploy/db/Dockerfile.mariadb @@ -6,6 +6,7 @@ RUN \ | xargs -rt -0 sed -Ei 's/^(bind-address|log)/#&/'; COPY mariadb-entrypoint.sh /docker-entrypoint.sh +COPY mariadb-ping.sh /dbping.sh # ENTRYPOINT ENTRYPOINT ["/docker-entrypoint.sh"] diff --git a/deploy/db/mariadb-ping.sh b/deploy/db/mariadb-ping.sh new file mode 100755 index 0000000000..331f6bb5a0 --- /dev/null +++ b/deploy/db/mariadb-ping.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +mysqladmin --password="$MYSQL_ROOT_PASSWORD" --user=root ping \ No newline at end of file diff --git a/deploy/kubernetes/console/templates/deployment.yaml b/deploy/kubernetes/console/templates/deployment.yaml index 1f36fc9548..25f1fd095a 100644 --- a/deploy/kubernetes/console/templates/deployment.yaml +++ b/deploy/kubernetes/console/templates/deployment.yaml @@ -353,7 +353,7 @@ spec: key: mariadb-password {{- end }} {{- else }} - - name: ALLOW_EMPTY_PASSWORD + - name: MYSQL_ALLOW_EMPTY_PASSWORD value: "yes" {{- end }} - name: MYSQL_USER @@ -366,15 +366,13 @@ spec: livenessProbe: exec: command: - - mysqladmin - - ping - initialDelaySeconds: 30 + - /dbping.sh + initialDelaySeconds: 10 timeoutSeconds: 5 readinessProbe: exec: command: - - mysqladmin - - ping + - /dbping.sh initialDelaySeconds: 5 timeoutSeconds: 1 resources: diff --git a/deploy/stratos-base-images/Dockerfile.stratos-bk-base.tmpl b/deploy/stratos-base-images/Dockerfile.stratos-bk-base.tmpl index 52089892b3..8416e9ea83 100644 --- a/deploy/stratos-base-images/Dockerfile.stratos-bk-base.tmpl +++ b/deploy/stratos-base-images/Dockerfile.stratos-bk-base.tmpl @@ -9,6 +9,8 @@ RUN zypper addrepo -t rpm-md -G -c '{{SMT_INTERNAL_SERVER}}' smt_internal_server RUN zypper ref {{/IS_SLE}} RUN zypper in -y ca-certificates && \ + # Can remove this when we remove the postflight job + zypper in -y mariadb-client curl && \ mkdir -p /srv && \ mkdir -p /root/.npm-global # Install latest git from devel/tools/scm repository diff --git a/deploy/stratos-base-images/Dockerfile.stratos-mariadb-base.tmpl b/deploy/stratos-base-images/Dockerfile.stratos-mariadb-base.tmpl index 428811512b..7c4cce74ff 100644 --- a/deploy/stratos-base-images/Dockerfile.stratos-mariadb-base.tmpl +++ b/deploy/stratos-base-images/Dockerfile.stratos-mariadb-base.tmpl @@ -13,7 +13,7 @@ RUN zypper ref ENV MYSQL_ROOT_PASSWORD mysecretpassword # Add repo for the latest mariadb -RUN zypper in -y mariadb net-tools mariadb-tools timezone wget && \ +RUN zypper in -y mariadb net-tools mariadb-tools timezone wget awk grep && \ zypper clean -a && \ rm -f /var/log/zypper.log /var/log/zypp/history From 3a6b296de07de8b34c75d5c614ee9d03a0844cd3 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 30 Oct 2019 20:30:28 +0000 Subject: [PATCH 10/29] Use tag that won't break existing builds --- deploy/Dockerfile.all-in-one | 4 ++-- deploy/Dockerfile.bk | 10 +++++----- deploy/Dockerfile.ui | 6 +++--- deploy/ci/scripts/Dockerfile.stratos-ci | 2 +- deploy/containers/nginx/Dockerfile.dc | 2 +- deploy/db/Dockerfile.mariadb | 2 +- deploy/stratos-base-images/build-base-images.sh | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/deploy/Dockerfile.all-in-one b/deploy/Dockerfile.all-in-one index ed3391cea7..548701d6f4 100644 --- a/deploy/Dockerfile.all-in-one +++ b/deploy/Dockerfile.all-in-one @@ -1,5 +1,5 @@ # Docker build for all-in-one Stratos -FROM splatform/stratos-aio-base:opensuse as builder +FROM splatform/stratos-aio-base:leap15.1 as builder # Ensure that we copy the custom-src folder COPY --chown=stratos:users . ./ @@ -17,7 +17,7 @@ RUN CERTS_PATH=/home/stratos/dev-certs ./generate_cert.sh \ && chmod +x jetstream # use --target=aio to build All-in-one image -FROM splatform/stratos-bk-base:opensuse +FROM splatform/stratos-bk-base:leap15.1 ARG CANARY_BUILD COPY --from=builder /home/stratos/deploy/db /src/deploy/db COPY --from=builder /home/stratos/dev-certs /srv/dev-certs diff --git a/deploy/Dockerfile.bk b/deploy/Dockerfile.bk index 41ec20f1fb..e6d484a1e0 100644 --- a/deploy/Dockerfile.bk +++ b/deploy/Dockerfile.bk @@ -1,4 +1,4 @@ -FROM splatform/stratos-bk-build-base:opensuse as builder +FROM splatform/stratos-bk-build-base:leap15.1 as builder ARG stratos_version RUN mkdir -p /home/stratos WORKDIR /home/stratos @@ -7,12 +7,12 @@ RUN go version RUN npm install RUN npm run build-backend -FROM splatform/stratos-bk-base:opensuse as common-build +FROM splatform/stratos-bk-base:leap15.1 as common-build COPY --from=builder /home/stratos/src/jetstream/jetstream /srv/ RUN chmod +x /srv/jetstream # use --target=db-migrator to build db-migrator image -FROM splatform/stratos-bk-base:opensuse as db-migrator +FROM splatform/stratos-bk-base:leap15.1 as db-migrator WORKDIR /src COPY deploy/db/dbconf.yml db/dbconf.yml COPY deploy/db/scripts/development.sh . @@ -23,7 +23,7 @@ RUN chmod +x development.sh CMD bash /src/wait-for-it.sh -t 90 mariadb:3306 && bash /src/development.sh # use --target=postflight-job to build prod postflight-job -FROM splatform/stratos-bk-base:opensuse as postflight-job +FROM splatform/stratos-bk-base:leap15.1 as postflight-job RUN zypper -n in mariadb-client COPY --from=common-build /srv/jetstream /usr/local/bin/jetstream COPY deploy/db/dbconf.yml db/dbconf.yml @@ -31,7 +31,7 @@ COPY deploy/db/scripts/run-postflight-job.k8s.sh /run-postflight-job.sh CMD ["/run-postflight-job.sh"] # use --target=prod-build to build a backend image for Kubernetes -FROM splatform/stratos-bk-base:opensuse as prod-build +FROM splatform/stratos-bk-base:leap15.1 as prod-build RUN zypper in -y curl COPY deploy/containers/proxy/entrypoint.sh /entrypoint.sh COPY /deploy/db/scripts/run-preflight-job.sh /run-preflight-job.sh diff --git a/deploy/Dockerfile.ui b/deploy/Dockerfile.ui index 66856bb28c..0aadaf1e10 100644 --- a/deploy/Dockerfile.ui +++ b/deploy/Dockerfile.ui @@ -1,4 +1,4 @@ -FROM splatform/stratos-ui-build-base:opensuse as base-build +FROM splatform/stratos-ui-build-base:leap15.1 as base-build ARG project ARG branch ARG commit @@ -13,7 +13,7 @@ RUN npm install && \ mkdir -p /usr/dist && \ cp -R dist/* /usr/dist -FROM splatform/stratos-nginx-base:opensuse as prod-build +FROM splatform/stratos-nginx-base:leap15.1 as prod-build RUN mkdir -p /usr/share/doc/suse COPY deploy/containers/nginx/LICENSE.txt /usr/share/doc/suse/LICENSE.txt COPY deploy/containers/nginx/conf/nginx.k8s.conf /etc/nginx/nginx.conf @@ -22,7 +22,7 @@ COPY deploy/containers/nginx/run-nginx.sh/ /run-nginx.sh EXPOSE 80 443 CMD [ "/run-nginx.sh" ] -FROM splatform/stratos-nginx-base:opensuse as dev-build +FROM splatform/stratos-nginx-base:leap15.1 as dev-build RUN mkdir -p /etc/secrets/ && \ openssl req -batch -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout /etc/secrets/server.key -out /etc/secrets/server.crt && \ chmod 0600 /etc/secrets && \ diff --git a/deploy/ci/scripts/Dockerfile.stratos-ci b/deploy/ci/scripts/Dockerfile.stratos-ci index a9c9dcbc1d..77a3db7b5a 100644 --- a/deploy/ci/scripts/Dockerfile.stratos-ci +++ b/deploy/ci/scripts/Dockerfile.stratos-ci @@ -6,7 +6,7 @@ # Default Image used to run tasks - contains Helm # Builder for the github release tool -FROM splatform/stratos-go-build-base:opensuse as go-base +FROM splatform/stratos-go-build-base:leap15.1 as go-base RUN export GOPATH=/home/stratos/go && \ mkdir -p ${GOPATH} && \ go get github.com/aktau/github-release diff --git a/deploy/containers/nginx/Dockerfile.dc b/deploy/containers/nginx/Dockerfile.dc index 7af4708169..4fb64dd3e4 100644 --- a/deploy/containers/nginx/Dockerfile.dc +++ b/deploy/containers/nginx/Dockerfile.dc @@ -1,4 +1,4 @@ -FROM splatform/stratos-nginx-base:opensuse +FROM splatform/stratos-nginx-base:leap15.1 RUN mkdir -p /etc/secrets/ && \ openssl req -batch -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout /etc/secrets/server.key -out /etc/secrets/server.crt && \ diff --git a/deploy/db/Dockerfile.mariadb b/deploy/db/Dockerfile.mariadb index 16ccff17a2..a6f7b4183e 100644 --- a/deploy/db/Dockerfile.mariadb +++ b/deploy/db/Dockerfile.mariadb @@ -1,4 +1,4 @@ -FROM splatform/stratos-db-base:opensuse +FROM splatform/stratos-db-base:leap15.1 RUN \ find /etc/ -name 'my*.cnf' -print0 \ diff --git a/deploy/stratos-base-images/build-base-images.sh b/deploy/stratos-base-images/build-base-images.sh index f29006238e..a7a653e2e1 100755 --- a/deploy/stratos-base-images/build-base-images.sh +++ b/deploy/stratos-base-images/build-base-images.sh @@ -10,7 +10,7 @@ BOLD="\033[1m" BASE_IMAGE=opensuse/leap:15.1 REGISTRY=docker.io ORGANIZATION=splatform -TAG=opensuse +TAG=leap15.1 PROG=$(basename ${BASH_SOURCE[0]}) SQUASH_ARGS="--squash" NO_SQUASH="stratos-base" From b21e9b24c8b2774de8fa41060f2169fffe495318 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 30 Oct 2019 20:34:21 +0000 Subject: [PATCH 11/29] Label the script with its origin --- deploy/db/mariadb-entrypoint.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/deploy/db/mariadb-entrypoint.sh b/deploy/db/mariadb-entrypoint.sh index 329d73569d..48e04e3142 100755 --- a/deploy/db/mariadb-entrypoint.sh +++ b/deploy/db/mariadb-entrypoint.sh @@ -1,4 +1,13 @@ #!/bin/bash + +########################################################################################################### +# +# This is the entrypoint script taken from here: https://github.com/docker-library/mariadb/tree/master/10.2 +# +# There is one change - which is clearly marked below +# +########################################################################################################### + set -eo pipefail shopt -s nullglob From 7231c355899f51cbda9fd6537b8e020f688652b3 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 30 Oct 2019 21:10:10 +0000 Subject: [PATCH 12/29] Add comment --- deploy/db/Dockerfile.mariadb | 1 + 1 file changed, 1 insertion(+) diff --git a/deploy/db/Dockerfile.mariadb b/deploy/db/Dockerfile.mariadb index a6f7b4183e..4bd2dd4724 100644 --- a/deploy/db/Dockerfile.mariadb +++ b/deploy/db/Dockerfile.mariadb @@ -1,5 +1,6 @@ FROM splatform/stratos-db-base:leap15.1 +# See: https://github.com/docker-library/mariadb/blob/master/10.2/Dockerfile RUN \ find /etc/ -name 'my*.cnf' -print0 \ | xargs -0 grep -lZE '^(bind-address|log)' \ From 25c9eb13df398e3bf5039761264167adceea490b Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 31 Oct 2019 08:53:43 +0000 Subject: [PATCH 13/29] Don't use . in tag as this causes issues with regex replacement --- deploy/Dockerfile.all-in-one | 4 ++-- deploy/Dockerfile.bk | 10 +++++----- deploy/Dockerfile.ui | 6 +++--- deploy/ci/scripts/Dockerfile.stratos-ci | 2 +- deploy/containers/nginx/Dockerfile.dc | 2 +- deploy/db/Dockerfile.mariadb | 2 +- deploy/stratos-base-images/build-base-images.sh | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/deploy/Dockerfile.all-in-one b/deploy/Dockerfile.all-in-one index 548701d6f4..ce046c3325 100644 --- a/deploy/Dockerfile.all-in-one +++ b/deploy/Dockerfile.all-in-one @@ -1,5 +1,5 @@ # Docker build for all-in-one Stratos -FROM splatform/stratos-aio-base:leap15.1 as builder +FROM splatform/stratos-aio-base:leap15_1 as builder # Ensure that we copy the custom-src folder COPY --chown=stratos:users . ./ @@ -17,7 +17,7 @@ RUN CERTS_PATH=/home/stratos/dev-certs ./generate_cert.sh \ && chmod +x jetstream # use --target=aio to build All-in-one image -FROM splatform/stratos-bk-base:leap15.1 +FROM splatform/stratos-bk-base:leap15_1 ARG CANARY_BUILD COPY --from=builder /home/stratos/deploy/db /src/deploy/db COPY --from=builder /home/stratos/dev-certs /srv/dev-certs diff --git a/deploy/Dockerfile.bk b/deploy/Dockerfile.bk index e6d484a1e0..8317187079 100644 --- a/deploy/Dockerfile.bk +++ b/deploy/Dockerfile.bk @@ -1,4 +1,4 @@ -FROM splatform/stratos-bk-build-base:leap15.1 as builder +FROM splatform/stratos-bk-build-base:leap15_1 as builder ARG stratos_version RUN mkdir -p /home/stratos WORKDIR /home/stratos @@ -7,12 +7,12 @@ RUN go version RUN npm install RUN npm run build-backend -FROM splatform/stratos-bk-base:leap15.1 as common-build +FROM splatform/stratos-bk-base:leap15_1 as common-build COPY --from=builder /home/stratos/src/jetstream/jetstream /srv/ RUN chmod +x /srv/jetstream # use --target=db-migrator to build db-migrator image -FROM splatform/stratos-bk-base:leap15.1 as db-migrator +FROM splatform/stratos-bk-base:leap15_1 as db-migrator WORKDIR /src COPY deploy/db/dbconf.yml db/dbconf.yml COPY deploy/db/scripts/development.sh . @@ -23,7 +23,7 @@ RUN chmod +x development.sh CMD bash /src/wait-for-it.sh -t 90 mariadb:3306 && bash /src/development.sh # use --target=postflight-job to build prod postflight-job -FROM splatform/stratos-bk-base:leap15.1 as postflight-job +FROM splatform/stratos-bk-base:leap15_1 as postflight-job RUN zypper -n in mariadb-client COPY --from=common-build /srv/jetstream /usr/local/bin/jetstream COPY deploy/db/dbconf.yml db/dbconf.yml @@ -31,7 +31,7 @@ COPY deploy/db/scripts/run-postflight-job.k8s.sh /run-postflight-job.sh CMD ["/run-postflight-job.sh"] # use --target=prod-build to build a backend image for Kubernetes -FROM splatform/stratos-bk-base:leap15.1 as prod-build +FROM splatform/stratos-bk-base:leap15_1 as prod-build RUN zypper in -y curl COPY deploy/containers/proxy/entrypoint.sh /entrypoint.sh COPY /deploy/db/scripts/run-preflight-job.sh /run-preflight-job.sh diff --git a/deploy/Dockerfile.ui b/deploy/Dockerfile.ui index 0aadaf1e10..19ee495543 100644 --- a/deploy/Dockerfile.ui +++ b/deploy/Dockerfile.ui @@ -1,4 +1,4 @@ -FROM splatform/stratos-ui-build-base:leap15.1 as base-build +FROM splatform/stratos-ui-build-base:leap15_1 as base-build ARG project ARG branch ARG commit @@ -13,7 +13,7 @@ RUN npm install && \ mkdir -p /usr/dist && \ cp -R dist/* /usr/dist -FROM splatform/stratos-nginx-base:leap15.1 as prod-build +FROM splatform/stratos-nginx-base:leap15_1 as prod-build RUN mkdir -p /usr/share/doc/suse COPY deploy/containers/nginx/LICENSE.txt /usr/share/doc/suse/LICENSE.txt COPY deploy/containers/nginx/conf/nginx.k8s.conf /etc/nginx/nginx.conf @@ -22,7 +22,7 @@ COPY deploy/containers/nginx/run-nginx.sh/ /run-nginx.sh EXPOSE 80 443 CMD [ "/run-nginx.sh" ] -FROM splatform/stratos-nginx-base:leap15.1 as dev-build +FROM splatform/stratos-nginx-base:leap15_1 as dev-build RUN mkdir -p /etc/secrets/ && \ openssl req -batch -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout /etc/secrets/server.key -out /etc/secrets/server.crt && \ chmod 0600 /etc/secrets && \ diff --git a/deploy/ci/scripts/Dockerfile.stratos-ci b/deploy/ci/scripts/Dockerfile.stratos-ci index 77a3db7b5a..b2e9ec19b7 100644 --- a/deploy/ci/scripts/Dockerfile.stratos-ci +++ b/deploy/ci/scripts/Dockerfile.stratos-ci @@ -6,7 +6,7 @@ # Default Image used to run tasks - contains Helm # Builder for the github release tool -FROM splatform/stratos-go-build-base:leap15.1 as go-base +FROM splatform/stratos-go-build-base:leap15_1 as go-base RUN export GOPATH=/home/stratos/go && \ mkdir -p ${GOPATH} && \ go get github.com/aktau/github-release diff --git a/deploy/containers/nginx/Dockerfile.dc b/deploy/containers/nginx/Dockerfile.dc index 4fb64dd3e4..049c6c9a5a 100644 --- a/deploy/containers/nginx/Dockerfile.dc +++ b/deploy/containers/nginx/Dockerfile.dc @@ -1,4 +1,4 @@ -FROM splatform/stratos-nginx-base:leap15.1 +FROM splatform/stratos-nginx-base:leap15_1 RUN mkdir -p /etc/secrets/ && \ openssl req -batch -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout /etc/secrets/server.key -out /etc/secrets/server.crt && \ diff --git a/deploy/db/Dockerfile.mariadb b/deploy/db/Dockerfile.mariadb index 4bd2dd4724..885f8d3cc3 100644 --- a/deploy/db/Dockerfile.mariadb +++ b/deploy/db/Dockerfile.mariadb @@ -1,4 +1,4 @@ -FROM splatform/stratos-db-base:leap15.1 +FROM splatform/stratos-db-base:leap15_1 # See: https://github.com/docker-library/mariadb/blob/master/10.2/Dockerfile RUN \ diff --git a/deploy/stratos-base-images/build-base-images.sh b/deploy/stratos-base-images/build-base-images.sh index a7a653e2e1..9675015cd6 100755 --- a/deploy/stratos-base-images/build-base-images.sh +++ b/deploy/stratos-base-images/build-base-images.sh @@ -10,7 +10,7 @@ BOLD="\033[1m" BASE_IMAGE=opensuse/leap:15.1 REGISTRY=docker.io ORGANIZATION=splatform -TAG=leap15.1 +TAG=leap15_1 PROG=$(basename ${BASH_SOURCE[0]}) SQUASH_ARGS="--squash" NO_SQUASH="stratos-base" From d23158e6606fe92d6c7205cf46a8ea7f22491cfa Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 3 Dec 2019 11:45:44 +0000 Subject: [PATCH 14/29] Fix tokens table primary key --- src/jetstream/datastore/20191008121900_PrimaryKeys.go | 10 +++++++++- src/jetstream/repository/tokens/pgsql_tokens.go | 10 +++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/jetstream/datastore/20191008121900_PrimaryKeys.go b/src/jetstream/datastore/20191008121900_PrimaryKeys.go index 421bebfbcf..5752834268 100644 --- a/src/jetstream/datastore/20191008121900_PrimaryKeys.go +++ b/src/jetstream/datastore/20191008121900_PrimaryKeys.go @@ -10,12 +10,20 @@ import ( func init() { RegisterMigration(20191008121900, "PrimaryKeys", func(txn *sql.Tx, conf *goose.DBConf) error { + // Make sure all tokens have a CNSI guid + ensureTokensHaveCnsi := "UPDATE tokens SET cnsi_guid='STRATOS' WHERE token_type='uaa' and cnsi_guid IS NULL;" + _, err := txn.Exec(ensureTokensHaveCnsi) + if err != nil { + return err + } + // Note: SQLite does not allow constraints to be added after table creation if strings.Contains(conf.Driver.Name, "sqlite3") { return nil } - addTokensPrimaryKey := "ALTER TABLE tokens ADD CONSTRAINT PK_Tokens PRIMARY KEY (user_guid, token_guid);" + // Need cnsi_guid to not be NULL in order to be able to create this primary key + addTokensPrimaryKey := "ALTER TABLE tokens ADD CONSTRAINT PK_Tokens PRIMARY KEY (user_guid, cnsi_guid, token_guid);" _, err := txn.Exec(addTokensPrimaryKey) if err != nil { return err diff --git a/src/jetstream/repository/tokens/pgsql_tokens.go b/src/jetstream/repository/tokens/pgsql_tokens.go index 427323ef83..37e715551c 100644 --- a/src/jetstream/repository/tokens/pgsql_tokens.go +++ b/src/jetstream/repository/tokens/pgsql_tokens.go @@ -14,18 +14,18 @@ import ( var findAuthToken = `SELECT token_guid, auth_token, refresh_token, token_expiry, auth_type, meta_data FROM tokens - WHERE token_type = 'uaa' AND user_guid = $1` + WHERE token_type = 'uaa' AND cnsi_guid = 'STRATOS' AND user_guid = $1` var countAuthTokens = `SELECT COUNT(*) FROM tokens - WHERE token_type = 'uaa' AND user_guid = $1` + WHERE token_type = 'uaa' AND cnsi_guid = 'STRATOS' AND user_guid = $1 ` -var insertAuthToken = `INSERT INTO tokens (token_guid, user_guid, token_type, auth_token, refresh_token, token_expiry) - VALUES ($1, $2, $3, $4, $5, $6)` +var insertAuthToken = `INSERT INTO tokens (cnsi_guid, token_guid, user_guid, token_type, auth_token, refresh_token, token_expiry) + VALUES ('STRATOS', $1, $2, $3, $4, $5, $6)` var updateAuthToken = `UPDATE tokens SET auth_token = $1, refresh_token = $2, token_expiry = $3 - WHERE user_guid = $4 AND token_type = $5` + WHERE cnsi_guid = 'STRATOS' AND user_guid = $4 AND token_type = $5` var getToken = `SELECT token_guid, auth_token, refresh_token, token_expiry, disconnected, auth_type, meta_data, user_guid, linked_token FROM tokens From 175bc90d41ada5be5436b1ef4e93a2f637bbea24 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 3 Dec 2019 12:43:30 +0000 Subject: [PATCH 15/29] Fix unit test issues --- src/jetstream/datastore/20191008121900_PrimaryKeys.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jetstream/datastore/20191008121900_PrimaryKeys.go b/src/jetstream/datastore/20191008121900_PrimaryKeys.go index 5752834268..9e1210616f 100644 --- a/src/jetstream/datastore/20191008121900_PrimaryKeys.go +++ b/src/jetstream/datastore/20191008121900_PrimaryKeys.go @@ -24,7 +24,7 @@ func init() { // Need cnsi_guid to not be NULL in order to be able to create this primary key addTokensPrimaryKey := "ALTER TABLE tokens ADD CONSTRAINT PK_Tokens PRIMARY KEY (user_guid, cnsi_guid, token_guid);" - _, err := txn.Exec(addTokensPrimaryKey) + _, err = txn.Exec(addTokensPrimaryKey) if err != nil { return err } From d2aff018132fde1010222bb5da597b4aebcf2161 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 3 Dec 2019 13:34:52 +0000 Subject: [PATCH 16/29] Missing continuation at end of line --- .../stratos-base-images/Dockerfile.stratos-bk-build-base.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/stratos-base-images/Dockerfile.stratos-bk-build-base.tmpl b/deploy/stratos-base-images/Dockerfile.stratos-bk-build-base.tmpl index a3f54a9391..c0009f86ab 100644 --- a/deploy/stratos-base-images/Dockerfile.stratos-bk-build-base.tmpl +++ b/deploy/stratos-base-images/Dockerfile.stratos-bk-build-base.tmpl @@ -6,7 +6,7 @@ RUN useradd -ms /bin/bash stratos && \ chgrp -R users /home/stratos RUN cd / && wget https://nodejs.org/dist/v12.13.0/node-v12.13.0-linux-x64.tar.xz && \ - tar -xf node-v12.13.0-linux-x64.tar.xz + tar -xf node-v12.13.0-linux-x64.tar.xz && \ rm node-v12.3.0-linux-x64.tar.xz ENV USER=stratos ENV PATH=$PATH:/node-v12.13.0-linux-x64/bin From 3e579f46ac3cc233f0b79af886cc519fe8d25840 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 3 Dec 2019 15:15:43 +0000 Subject: [PATCH 17/29] Fix incorrect number --- .../stratos-base-images/Dockerfile.stratos-bk-build-base.tmpl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/deploy/stratos-base-images/Dockerfile.stratos-bk-build-base.tmpl b/deploy/stratos-base-images/Dockerfile.stratos-bk-build-base.tmpl index c0009f86ab..04137d442f 100644 --- a/deploy/stratos-base-images/Dockerfile.stratos-bk-build-base.tmpl +++ b/deploy/stratos-base-images/Dockerfile.stratos-bk-build-base.tmpl @@ -7,8 +7,10 @@ RUN useradd -ms /bin/bash stratos && \ RUN cd / && wget https://nodejs.org/dist/v12.13.0/node-v12.13.0-linux-x64.tar.xz && \ tar -xf node-v12.13.0-linux-x64.tar.xz && \ - rm node-v12.3.0-linux-x64.tar.xz + rm node-v12.13.0-linux-x64.tar.xz ENV USER=stratos ENV PATH=$PATH:/node-v12.13.0-linux-x64/bin USER stratos WORKDIR /home/stratos + + From ad2cb37cdc3106bebf7b3b1df4da778472627511 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 3 Dec 2019 15:26:17 +0000 Subject: [PATCH 18/29] Remove intermediate containers --- deploy/stratos-base-images/build-base-images.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deploy/stratos-base-images/build-base-images.sh b/deploy/stratos-base-images/build-base-images.sh index 9675015cd6..2e58c97a4c 100755 --- a/deploy/stratos-base-images/build-base-images.sh +++ b/deploy/stratos-base-images/build-base-images.sh @@ -124,7 +124,8 @@ build_and_push_image() { ARG="${SQUASH_ARGS}" fi set -x - docker build ${ARG} . -f $docker_file -t ${REGISTRY}/${ORGANIZATION}/${image_name}:${TAG} + # Always remove intermediate containers + docker build --force-rm ${ARG} . -f $docker_file -t ${REGISTRY}/${ORGANIZATION}/${image_name}:${TAG} if [ ! -z ${PUSH_IMAGES} ]; then docker push ${REGISTRY}/${ORGANIZATION}/${image_name}:${TAG} fi From 78495cb8331c71c89cc357cfc373c5355d2f54e8 Mon Sep 17 00:00:00 2001 From: Nathan Jones Date: Fri, 29 Nov 2019 09:45:02 +0000 Subject: [PATCH 19/29] Fix incorrect http client params --- .../src/actions/organization.actions.ts | 6 +++--- .../cloud-foundry/src/actions/relation.actions.ts | 4 ++-- .../src/actions/service-instances.actions.ts | 12 +++++++----- .../cloud-foundry/src/actions/space.actions.ts | 12 +++++++----- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/frontend/packages/cloud-foundry/src/actions/organization.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/organization.actions.ts index 61bbeef953..58077aa52f 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/organization.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/organization.actions.ts @@ -18,7 +18,7 @@ import { } from '../entity-relations/entity-relations.types'; import { CFStartAction } from './cf-action.types'; import { createDefaultUserRelations } from './user.actions.helpers'; -import { HttpRequest } from '@angular/common/http'; +import { HttpRequest, HttpHeaders } from '@angular/common/http'; export const GET_ORGANIZATION = '[Organization] Get one'; export const GET_ORGANIZATION_SUCCESS = '[Organization] Get one success'; @@ -172,10 +172,10 @@ export class DeleteOrganization extends CFStartAction implements ICFAction { 'DELETE', `organizations/${guid}`, { - params: { + params: new HttpHeaders({ recursive: 'true', async: 'false' - } + }) } ); } diff --git a/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts index ade4c360b2..bbf82324c9 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts @@ -7,7 +7,7 @@ import { PaginatedAction } from '../../../store/src/types/pagination.types'; import { RequestEntityLocation, RequestActionEntity } from '../../../store/src/types/request.types'; import { CFStartAction } from './cf-action.types'; import { EntityTreeRelation } from '../entity-relations/entity-relation-tree'; -import { HttpRequest } from '@angular/common/http'; +import { HttpRequest, HttpParams } from '@angular/common/http'; const relationActionId = 'FetchRelationAction'; @@ -27,7 +27,7 @@ export abstract class FetchRelationAction extends CFStartAction implements Entit 'GET', url.startsWith('/v2/') ? url.substring(4, url.length) : url, { - params: {} + params: new HttpParams() } ); this.parentEntityConfig = parent.entity; diff --git a/src/frontend/packages/cloud-foundry/src/actions/service-instances.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/service-instances.actions.ts index 44654b6f0f..9f0a3f6a55 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/service-instances.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/service-instances.actions.ts @@ -84,11 +84,13 @@ export class DeleteServiceInstance extends CFStartAction implements ICFAction { 'DELETE', `service_instances/${guid}`, { - params: { - accepts_incomplete: 'true', - async: 'false', - recursive: 'true' - } + params: new HttpParams({ + fromObject: { + accepts_incomplete: 'true', + async: 'false', + recursive: 'true' + } + }) } ); } diff --git a/src/frontend/packages/cloud-foundry/src/actions/space.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/space.actions.ts index 4d3387dd25..0f4677be84 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/space.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/space.actions.ts @@ -24,7 +24,7 @@ import { CFStartAction } from './cf-action.types'; import { GetAllOrgUsers } from './organization.actions'; import { RouteEvents } from './route.actions'; import { getServiceInstanceRelations } from './service-instances.actions'; -import { HttpRequest } from '@angular/common/http'; +import { HttpRequest, HttpParams } from '@angular/common/http'; export const GET_SPACES = '[Space] Get all'; export const GET_SPACES_SUCCESS = '[Space] Get all success'; @@ -177,10 +177,12 @@ export class DeleteSpace extends BaseSpaceAction { 'DELETE', `spaces/${guid}`, { - params: { - recursive: 'true', - async: 'false' - } + params: new HttpParams({ + fromObject: { + recursive: 'true', + async: 'false' + } + }) } ); } From 1668108bc462e89d7ceb288cb6a599656591f17b Mon Sep 17 00:00:00 2001 From: Nathan Jones Date: Fri, 29 Nov 2019 13:37:43 +0000 Subject: [PATCH 20/29] Typo in get all space action --- .../packages/cloud-foundry/src/actions/space.actions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/packages/cloud-foundry/src/actions/space.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/space.actions.ts index 0f4677be84..53c27a466b 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/space.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/space.actions.ts @@ -75,7 +75,7 @@ export class GetAllSpaces extends CFStartAction implements PaginatedAction, Enti super(); this.options = new HttpRequest( 'GET', - 'spsace' + 'space' ); } actions = [GET_SPACES, GET_SPACES_SUCCESS, GET_SPACES_FAILED]; From 71ed7d71e75ddbda642471e358886bdfe1736458 Mon Sep 17 00:00:00 2001 From: Nathan Jones Date: Mon, 2 Dec 2019 15:49:34 +0000 Subject: [PATCH 21/29] Fix unit tests --- ...api-entity-type-selector.component.spec.ts | 6 ++-- ...api-endpoint-select-page.component.spec.ts | 20 ++++++++++-- .../api-endpoint-select-page.component.ts | 6 ++-- ...ndpoint-type-select-page.component.spec.ts | 19 +++++++++++- .../api-entity-list-page.component.html | 2 +- .../api-entity-list-page.component.spec.ts | 18 +++++++++-- .../api-entity-list-page.component.ts | 2 +- ...-entity-type-select-page.component.spec.ts | 20 ++++++++++-- .../api-entity-type-select-page.component.ts | 7 +++-- .../simple-list/simple-list.component.spec.ts | 31 ++++++++++--------- 10 files changed, 100 insertions(+), 31 deletions(-) diff --git a/src/frontend/packages/core/src/api-driven-views/components/api-type-selector/api-entity-type-selector.component.spec.ts b/src/frontend/packages/core/src/api-driven-views/components/api-type-selector/api-entity-type-selector.component.spec.ts index 9f538b0477..b54398c933 100644 --- a/src/frontend/packages/core/src/api-driven-views/components/api-type-selector/api-entity-type-selector.component.spec.ts +++ b/src/frontend/packages/core/src/api-driven-views/components/api-type-selector/api-entity-type-selector.component.spec.ts @@ -3,15 +3,15 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ApiEntityType } from '../../api-drive-views.types'; import { SharedModule } from '../../../shared/shared.module'; import { ApiEntityTypeSelectorComponent } from './api-entity-type-selector.component'; +import { ApiDrivenViewsModule } from '../../api-driven-views.module'; -fdescribe('ApiEntityTypeSelectorComponent', () => { +describe('ApiEntityTypeSelectorComponent', () => { let component: ApiEntityTypeSelectorComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ApiEntityTypeSelectorComponent], - imports: [SharedModule] + imports: [SharedModule, ApiDrivenViewsModule] }) .compileComponents(); })); diff --git a/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-select-page/api-endpoint-select-page.component.spec.ts b/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-select-page/api-endpoint-select-page.component.spec.ts index 46d0bff541..8edb118f05 100644 --- a/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-select-page/api-endpoint-select-page.component.spec.ts +++ b/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-select-page/api-endpoint-select-page.component.spec.ts @@ -1,6 +1,14 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ApiEndpointSelectPageComponent } from './api-endpoint-select-page.component'; +import { SharedModule } from '../../../shared/shared.module'; +import { ApiDrivenViewsModule } from '../../api-driven-views.module'; +import { CoreModule } from '../../../core/core.module'; +import { RouterTestingModule } from '@angular/router/testing'; +import { createBasicStoreModule } from '../../../../test-framework/store-test-helper'; +import { TabNavService } from '../../../../tab-nav.service'; +import { EntityCatalogueModule } from '../../../core/entity-catalogue.module'; +import { generateStratosEntities } from '../../../base-entity-types'; describe('ApiEndpointSelectPageComponent', () => { let component: ApiEndpointSelectPageComponent; @@ -8,9 +16,17 @@ describe('ApiEndpointSelectPageComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ ApiEndpointSelectPageComponent ] + imports: [ + EntityCatalogueModule.forFeature(generateStratosEntities), + CoreModule, + RouterTestingModule, + SharedModule, + createBasicStoreModule(), + ApiDrivenViewsModule, + ], + providers: [TabNavService] }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { diff --git a/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-select-page/api-endpoint-select-page.component.ts b/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-select-page/api-endpoint-select-page.component.ts index 7bf4a74a36..4e0374aa43 100644 --- a/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-select-page/api-endpoint-select-page.component.ts +++ b/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-select-page/api-endpoint-select-page.component.ts @@ -5,7 +5,7 @@ import { ApiEntityType } from '../../api-drive-views.types'; import { Store } from '@ngrx/store'; import { connectedEndpointsOfTypesSelector } from '../../../../../store/src/selectors/endpoint.selectors'; import { ActivatedRoute, Router } from '@angular/router'; -import { map } from 'rxjs/operators'; +import { map, filter } from 'rxjs/operators'; @Component({ selector: 'app-api-endpoint-select-page', @@ -26,7 +26,9 @@ export class ApiEndpointSelectPageComponent implements OnInit { const endpointType = this.route.snapshot.params.endpointType; this.connectedEndpointsOfType$ = this.store.select(connectedEndpointsOfTypesSelector(endpointType)).pipe( - map(endpoints => Object.values(endpoints).map(endpoint => new ApiEntityType( + map(endpointsMap => Object.values(endpointsMap)), + filter(endpoints => !!endpoints || !endpoints.length), + map(endpoints => endpoints.map(endpoint => new ApiEntityType( endpoint.guid, endpoint.name // TODO Get icon from entity catalogue diff --git a/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-type-select-page/api-endpoint-type-select-page.component.spec.ts b/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-type-select-page/api-endpoint-type-select-page.component.spec.ts index f07f695835..ccf3a5249f 100644 --- a/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-type-select-page/api-endpoint-type-select-page.component.spec.ts +++ b/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-type-select-page/api-endpoint-type-select-page.component.spec.ts @@ -2,6 +2,15 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ApiEndpointTypeSelectPageComponent } from './api-endpoint-type-select-page.component'; import { ApiDrivenViewsModule } from '../../api-driven-views.module'; +import { StoreModule } from '@ngrx/store'; +import { appReducers } from '../../../../../store/src/reducers.module'; +import { RouterTestingModule } from '@angular/router/testing'; +import { CoreModule } from '../../../core/core.module'; +import { SharedModule } from '../../../shared/shared.module'; +import { createBasicStoreModule } from '../../../../test-framework/store-test-helper'; +import { TabNavService } from '../../../../tab-nav.service'; +import { EntityCatalogueModule } from '../../../core/entity-catalogue.module'; +import { generateStratosEntities } from '../../../base-entity-types'; describe('ApiEndpointTypeSelectPageComponent', () => { let component: ApiEndpointTypeSelectPageComponent; @@ -9,7 +18,15 @@ describe('ApiEndpointTypeSelectPageComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - imports: [ApiDrivenViewsModule] + imports: [ + EntityCatalogueModule.forFeature(generateStratosEntities), + CoreModule, + RouterTestingModule, + SharedModule, + createBasicStoreModule(), + ApiDrivenViewsModule, + ], + providers: [TabNavService] }) .compileComponents(); })); diff --git a/src/frontend/packages/core/src/api-driven-views/features/api-entity-list-page/api-entity-list-page.component.html b/src/frontend/packages/core/src/api-driven-views/features/api-entity-list-page/api-entity-list-page.component.html index 24546a949b..56a5ebb7f1 100644 --- a/src/frontend/packages/core/src/api-driven-views/features/api-entity-list-page/api-entity-list-page.component.html +++ b/src/frontend/packages/core/src/api-driven-views/features/api-entity-list-page/api-entity-list-page.component.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/frontend/packages/core/src/api-driven-views/features/api-entity-list-page/api-entity-list-page.component.spec.ts b/src/frontend/packages/core/src/api-driven-views/features/api-entity-list-page/api-entity-list-page.component.spec.ts index 8469756c7d..340228d85c 100644 --- a/src/frontend/packages/core/src/api-driven-views/features/api-entity-list-page/api-entity-list-page.component.spec.ts +++ b/src/frontend/packages/core/src/api-driven-views/features/api-entity-list-page/api-entity-list-page.component.spec.ts @@ -1,6 +1,13 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ApiEntityListPageComponent } from './api-entity-list-page.component'; +import { ApiDrivenViewsModule } from '../../api-driven-views.module'; +import { RouterTestingModule } from '@angular/router/testing'; +import { EntityCatalogueModule } from '../../../core/entity-catalogue.module'; +import { generateStratosEntities } from '../../../base-entity-types'; +import { CoreModule } from '../../../core/core.module'; +import { SharedModule } from '../../../shared/shared.module'; +import { createBasicStoreModule } from '../../../../test-framework/store-test-helper'; describe('ApiEntityListPageComponent', () => { let component: ApiEntityListPageComponent; @@ -8,9 +15,16 @@ describe('ApiEntityListPageComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ ApiEntityListPageComponent ] + imports: [ + EntityCatalogueModule.forFeature(generateStratosEntities), + CoreModule, + RouterTestingModule, + SharedModule, + createBasicStoreModule(), + ApiDrivenViewsModule, + ] }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { diff --git a/src/frontend/packages/core/src/api-driven-views/features/api-entity-list-page/api-entity-list-page.component.ts b/src/frontend/packages/core/src/api-driven-views/features/api-entity-list-page/api-entity-list-page.component.ts index 3458579617..8b2b897a91 100644 --- a/src/frontend/packages/core/src/api-driven-views/features/api-entity-list-page/api-entity-list-page.component.ts +++ b/src/frontend/packages/core/src/api-driven-views/features/api-entity-list-page/api-entity-list-page.component.ts @@ -15,7 +15,7 @@ export class ApiEntityListPageComponent implements OnInit { ) { } ngOnInit() { - const endpointType = this.route.parent.snapshot.params.endpointType; + const endpointType = this.route.parent ? this.route.parent.snapshot.params.endpointType : null; const entityType = this.route.snapshot.params.entityType; this.catalogueEntity = entityCatalogue.getEntity(endpointType, entityType); } diff --git a/src/frontend/packages/core/src/api-driven-views/features/api-entity-type-select-page/api-entity-type-select-page.component.spec.ts b/src/frontend/packages/core/src/api-driven-views/features/api-entity-type-select-page/api-entity-type-select-page.component.spec.ts index 257136f53e..1dcdf1be51 100644 --- a/src/frontend/packages/core/src/api-driven-views/features/api-entity-type-select-page/api-entity-type-select-page.component.spec.ts +++ b/src/frontend/packages/core/src/api-driven-views/features/api-entity-type-select-page/api-entity-type-select-page.component.spec.ts @@ -1,6 +1,14 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ApiEntityTypeSelectPageComponent } from './api-entity-type-select-page.component'; +import { ApiDrivenViewsModule } from '../../api-driven-views.module'; +import { RouterTestingModule } from '@angular/router/testing'; +import { EntityCatalogueModule } from '../../../core/entity-catalogue.module'; +import { generateStratosEntities } from '../../../base-entity-types'; +import { CoreModule } from '../../../core/core.module'; +import { SharedModule } from '../../../shared/shared.module'; +import { createBasicStoreModule } from '../../../../test-framework/store-test-helper'; +import { TabNavService } from '../../../../tab-nav.service'; describe('ApiEntityTypeSelectPageComponent', () => { let component: ApiEntityTypeSelectPageComponent; @@ -8,9 +16,17 @@ describe('ApiEntityTypeSelectPageComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ ApiEntityTypeSelectPageComponent ] + imports: [ + EntityCatalogueModule.forFeature(generateStratosEntities), + CoreModule, + RouterTestingModule, + SharedModule, + createBasicStoreModule(), + ApiDrivenViewsModule, + ], + providers: [TabNavService] }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { diff --git a/src/frontend/packages/core/src/api-driven-views/features/api-entity-type-select-page/api-entity-type-select-page.component.ts b/src/frontend/packages/core/src/api-driven-views/features/api-entity-type-select-page/api-entity-type-select-page.component.ts index 0a9d57966c..f0dce95d7c 100644 --- a/src/frontend/packages/core/src/api-driven-views/features/api-entity-type-select-page/api-entity-type-select-page.component.ts +++ b/src/frontend/packages/core/src/api-driven-views/features/api-entity-type-select-page/api-entity-type-select-page.component.ts @@ -32,13 +32,14 @@ export class ApiEntityTypeSelectPageComponent implements OnInit { const endpointEntity = entityCatalogue.getEndpoint(endpointType); const endpointEntities = entityCatalogue.getAllEntitiesForEndpointType(endpointType); const entitiesWithGetMultiple = endpointEntities.filter( - entity => entity.definition.tableConfig && entity.actionOrchestrator.hasActionBuilder('getMultiple') + entity => entity && entity.definition.tableConfig && entity.actionOrchestrator.hasActionBuilder('getMultiple') ); this.connectedEndpointsOfType$ = this.store.select(connectedEndpointsOfTypesSelector(endpointType)).pipe( map(endpoints => endpoints[endpointGuid] ? endpoints[endpointGuid].name : 'Entities') ); - - this.tabNavService.setHeader(endpointEntity.definition.label); + if (endpointEntity) { + this.tabNavService.setHeader(endpointEntity.definition.label); + } this.tabs = entitiesWithGetMultiple.map(entity => { return { link: entity.type, diff --git a/src/frontend/packages/core/src/shared/components/list/simple-list/simple-list.component.spec.ts b/src/frontend/packages/core/src/shared/components/list/simple-list/simple-list.component.spec.ts index 3de9cf3725..67c87f35d7 100644 --- a/src/frontend/packages/core/src/shared/components/list/simple-list/simple-list.component.spec.ts +++ b/src/frontend/packages/core/src/shared/components/list/simple-list/simple-list.component.spec.ts @@ -9,6 +9,7 @@ import { StratosEndpointExtensionDefinition } from '../../../../core/entity-cata import { EntitySchema } from '../../../../../../store/src/helpers/entity-schema'; import { EntityCatalogueTestModule, TEST_CATALOGUE_ENTITIES } from '../../../../core/entity-catalogue-test.module'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { RouterTestingModule } from '@angular/router/testing'; describe('SimpleListComponent', () => { let component: SimpleListComponent; @@ -30,20 +31,20 @@ describe('SimpleListComponent', () => { schema: new EntitySchema('key', endpoint.type), endpoint, }, { - entityBuilder: { - getLines: () => ([]), - getMetadata: () => ({ name: 'test' }), - getGuid: () => 'test', - }, - actionBuilders: { - getMultiple: () => ({ - type: 'testAction', - paginationKey: 'testPagKey', - entityType: ceType, - endpointType: endpoint.type - }) - } - }); + entityBuilder: { + getLines: () => ([]), + getMetadata: () => ({ name: 'test' }), + getGuid: () => 'test', + }, + actionBuilders: { + getMultiple: () => ({ + type: 'testAction', + paginationKey: 'testPagKey', + entityType: ceType, + endpointType: endpoint.type + }) + } + }); beforeEach(async(() => { TestBed.configureTestingModule({ @@ -51,6 +52,8 @@ describe('SimpleListComponent', () => { SharedModule, CoreModule, AppReducersModule, + RouterTestingModule, + SharedModule, NoopAnimationsModule, { ngModule: EntityCatalogueTestModule, From bf3f4c5100c05605ab78b0061c7dffb28a9dccb8 Mon Sep 17 00:00:00 2001 From: Guillaume Berche Date: Wed, 4 Dec 2019 08:10:47 +0100 Subject: [PATCH 22/29] Mention STRATOS_BP_DEBUG to troubleshoot staging (#4039) --- manifest.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/manifest.yml b/manifest.yml index 9d229edd2b..fb4a04acad 100644 --- a/manifest.yml +++ b/manifest.yml @@ -13,6 +13,8 @@ applications: # CF_API_FORCE_SECURE: true # Turn on backend debugging # LOG_LEVEL: debug +# Turn on staging debugging in stratos-buildpack +# STRATOS_BP_DEBUG: true #Remove line to turn off debugging # User provided services can also be used to set environment properties: # env: From ab209bdce9b61a1f28614a6ea78795bf2385cf01 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 4 Dec 2019 09:54:50 +0000 Subject: [PATCH 23/29] Bump gopkg.in/yaml.v2 from 2.2.2 to 2.2.7 in /src/jetstream (#4033) Bumps [gopkg.in/yaml.v2](https://github.com/go-yaml/yaml) from 2.2.2 to 2.2.7. - [Release notes](https://github.com/go-yaml/yaml/releases) - [Commits](https://github.com/go-yaml/yaml/compare/v2.2.2...v2.2.7) Signed-off-by: dependabot-preview[bot] --- src/jetstream/go.mod | 2 +- src/jetstream/go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/jetstream/go.mod b/src/jetstream/go.mod index e7efd40bdb..00ad025086 100644 --- a/src/jetstream/go.mod +++ b/src/jetstream/go.mod @@ -72,7 +72,7 @@ require ( google.golang.org/appengine v1.5.0 // indirect gopkg.in/DATA-DOG/go-sqlmock.v1 v1.0.0-00010101000000-000000000000 gopkg.in/cheggaaa/pb.v1 v1.0.27 // indirect - gopkg.in/yaml.v2 v2.2.2 + gopkg.in/yaml.v2 v2.2.7 ) replace ( diff --git a/src/jetstream/go.sum b/src/jetstream/go.sum index 3319739e5a..f4e13fbd76 100644 --- a/src/jetstream/go.sum +++ b/src/jetstream/go.sum @@ -344,6 +344,8 @@ gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= From 9c4422850042249c55a5ee925fcbbe477891d16c Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Wed, 4 Dec 2019 20:26:33 +0000 Subject: [PATCH 24/29] Merge v2-master into master (#4044) * Follow redirect - needed for repo move * Update metric used for cells (#4009) - `firehose_value_metric_rep_unhealthy_cell` deprecated, now use `firehose_value_metric_rep_garden_health_check_failed` - Try new metric first and fall back on old * Fix incorrect SSO behaviour following 2.4.0 --> 2.6.0 upgrade (#4015) - affects db migration of console config table to new config table - fixes #4013 * 2.6.1 Release preparation * Ingress fix (#4024) * Fixes ingress and kube 1.16 version issue * Update Changelog * More test * Fix ingress tests * Mention STRATOS_BP_DEBUG to troubleshoot staging (#4039) * Fix following merge --- CHANGELOG.md | 14 ++++ deploy/ci/tasks/dev-releases/github-helper.sh | 2 +- deploy/kubernetes/build.sh | 32 +++++---- .../console/templates/__helpers.tpl | 18 +++++ .../console/templates/deployment.yaml | 8 +++ .../kubernetes/console/templates/ingress.yaml | 12 +++- .../console/tests/ingress_test.yaml | 8 ++- .../console/tests/kube_version_test.yaml | 31 ++++++++ package-lock.json | 2 +- package.json | 2 +- .../cloud-foundry-cell.service.ts | 46 +++++++++--- .../cloud-foundry-cells.component.ts | 13 +++- .../cf-app-instances-config.service.ts | 14 ++-- .../list-types/base-cf/base-cf-list-config.ts | 3 + .../cf-cell-health-list-config.service.ts | 38 +++++----- .../cf-cells/cf-cells-data-source.ts | 18 ++--- .../cf-cells/cf-cells-list-config.service.ts | 19 ++++- .../core/src/core/endpoints.service.ts | 15 ++-- .../features/cloud-foundry/cf-cell.helpers.ts | 71 +++++++++++++++++++ .../features/endpoints/endpoint-helpers.ts | 18 ++++- .../plugins/metrics/cloud_foundry.go | 2 +- .../repository/console_config/env_lookup.go | 10 ++- 22 files changed, 307 insertions(+), 89 deletions(-) create mode 100644 deploy/kubernetes/console/tests/kube_version_test.yaml create mode 100644 src/frontend/packages/core/src/features/cloud-foundry/cf-cell.helpers.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index b73fe024d9..d669ead0ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Change Log +## 2.6.1 + +[Full Changelog](https://github.com/cloudfoundry/stratos/compare/2.6.0...2.6.1) + +This release contains a few fixes: + +**Fixes:** + +- Helm Chart does not work with Kubernetes 1.16 [\#4022](https://github.com/cloudfoundry/stratos/issues/4022) +- Generated Ingress certificates during Kubernetes deployment are empty [\#4006](https://github.com/cloudfoundry/stratos/issues/4006) +- Kubernetes Ingress certificate is incorrectly set [\#4005](https://github.com/cloudfoundry/stratos/issues/4005) +- Update metric used for cells [\#4009](https://github.com/cloudfoundry/stratos/pull/4009) +- Fix incorrect SSO behaviour following 2.4.0 --> 2.6.0 upgrade [\#4015](https://github.com/cloudfoundry/stratos/pull/4015) + ## 2.6.0 [Full Changelog](https://github.com/cloudfoundry/stratos/compare/2.5.0...2.6.0) diff --git a/deploy/ci/tasks/dev-releases/github-helper.sh b/deploy/ci/tasks/dev-releases/github-helper.sh index 632d4353b0..a4b0765286 100644 --- a/deploy/ci/tasks/dev-releases/github-helper.sh +++ b/deploy/ci/tasks/dev-releases/github-helper.sh @@ -39,7 +39,7 @@ downloadReleaseFile() { local parser=". | map(select(.tag_name == \"$VERSION\"))[0].assets | map(select(.name == \"$FILE\"))[0].id" # Get release information from GitHub - curl -H "Authorization: token $TOKEN" -H "Accept: application/vnd.github.v3.raw" -s $GITHUB/repos/$REPO/releases > releases.json + curl -L -H "Authorization: token $TOKEN" -H "Accept: application/vnd.github.v3.raw" -s $GITHUB/repos/$REPO/releases > releases.json if [ $? -ne 0 ]; then echo "Could not download release information for ${REPO}" exit 1 diff --git a/deploy/kubernetes/build.sh b/deploy/kubernetes/build.sh index 0fcf535154..e7c2c0ad79 100755 --- a/deploy/kubernetes/build.sh +++ b/deploy/kubernetes/build.sh @@ -26,8 +26,9 @@ ADD_OFFICIAL_TAG="false" TAG_LATEST="false" NO_PUSH="true" DOCKER_REG_DEFAULTS="true" +CHART_ONLY="false" -while getopts ":ho:r:t:Tclb:On" opt; do +while getopts ":ho:r:t:Tclb:Op" opt; do case $opt in h) echo @@ -64,6 +65,9 @@ while getopts ":ho:r:t:Tclb:On" opt; do p) NO_PUSH="false" ;; + c) + CHART_ONLY="true" + ;; \?) echo "Invalid option: -${OPTARG}" >&2 exit 1 @@ -175,22 +179,24 @@ cleanup updateTagForRelease -# Build all of the components that make up the Console +if [ "${CHART_ONLY}" == "false" ]; then + # Build all of the components that make up the Console -log "-- Build & publish the runtime container image for Jetstream (backend)" -patchAndPushImage stratos-jetstream deploy/Dockerfile.bk "${STRATOS_PATH}" prod-build + log "-- Build & publish the runtime container image for Jetstream (backend)" + patchAndPushImage stratos-jetstream deploy/Dockerfile.bk "${STRATOS_PATH}" prod-build -# Build the postflight container -log "-- Build & publish the runtime container image for the postflight job" -patchAndPushImage stratos-postflight-job deploy/Dockerfile.bk "${STRATOS_PATH}" postflight-job + # Build the postflight container + log "-- Build & publish the runtime container image for the postflight job" + patchAndPushImage stratos-postflight-job deploy/Dockerfile.bk "${STRATOS_PATH}" postflight-job -# Build and push an image based on the mariab db container -log "-- Building/publishing MariaDB" -patchAndPushImage stratos-mariadb Dockerfile.mariadb "${STRATOS_PATH}/deploy/db" + # Build and push an image based on the mariab db container + log "-- Building/publishing MariaDB" + patchAndPushImage stratos-mariadb Dockerfile.mariadb "${STRATOS_PATH}/deploy/db" -# Build and push an image based on the nginx container (Front-end) -log "-- Building/publishing the runtime container image for the Console web server (frontend)" -patchAndPushImage stratos-console deploy/Dockerfile.ui "${STRATOS_PATH}" prod-build + # Build and push an image based on the nginx container (Front-end) + log "-- Building/publishing the runtime container image for the Console web server (frontend)" + patchAndPushImage stratos-console deploy/Dockerfile.ui "${STRATOS_PATH}" prod-build +fi log "-- Building Helm Chart" diff --git a/deploy/kubernetes/console/templates/__helpers.tpl b/deploy/kubernetes/console/templates/__helpers.tpl index 5511d09bad..c24f888f74 100644 --- a/deploy/kubernetes/console/templates/__helpers.tpl +++ b/deploy/kubernetes/console/templates/__helpers.tpl @@ -106,6 +106,24 @@ tls.crt: {{ $cert.Cert | b64enc }} tls.key: {{ $cert.Key | b64enc }} {{- end -}} +{{/* +Generate self-signed certificate for ingress if needed +*/}} +{{- define "console.generateIngressCertificate" -}} +{{- $altNames := list (printf "%s" .Values.console.service.ingress.host) (printf "%s.%s" (include "console.certName" .) .Release.Namespace ) ( printf "%s.%s.svc" (include "console.certName" .) .Release.Namespace ) -}} +{{- $ca := genCA "stratos-ca" 365 -}} +{{- $cert := genSignedCert ( include "console.certName" . ) nil $altNames 365 $ca -}} +{{- if .Values.console.service.ingress.tls.crt }} + tls.crt: {{ .Values.console.service.ingress.tls.crt | b64enc | quote }} +{{- else }} + tls.crt: {{ $cert.Cert | b64enc | quote }} +{{- end -}} +{{- if .Values.console.service.ingress.tls.key }} + tls.key: {{ .Values.console.service.ingress.tls.key | b64enc | quote }} +{{- else }} + tls.key: {{ $cert.Key | b64enc | quote }} +{{- end -}} +{{- end -}} {{/* Ingress Host from .Values.console.service diff --git a/deploy/kubernetes/console/templates/deployment.yaml b/deploy/kubernetes/console/templates/deployment.yaml index 25f1fd095a..e419592e6b 100644 --- a/deploy/kubernetes/console/templates/deployment.yaml +++ b/deploy/kubernetes/console/templates/deployment.yaml @@ -1,5 +1,9 @@ --- +{{- if semverCompare ">=1.16" (printf "%s.%s" .Capabilities.KubeVersion.Major .Capabilities.KubeVersion.Minor)}} +apiVersion: apps/v1 +{{- else }} apiVersion: apps/v1beta1 +{{- end }} kind: StatefulSet metadata: name: stratos @@ -309,7 +313,11 @@ spec: name: {{ .Values.console.templatesConfigMapName }} {{- end }} --- +{{- if semverCompare ">=1.16" (printf "%s.%s" .Capabilities.KubeVersion.Major .Capabilities.KubeVersion.Minor)}} +apiVersion: apps/v1 +{{- else }} apiVersion: extensions/v1beta1 +{{- end }} kind: Deployment metadata: name: stratos-db diff --git a/deploy/kubernetes/console/templates/ingress.yaml b/deploy/kubernetes/console/templates/ingress.yaml index 2578b52df5..f50266c71b 100644 --- a/deploy/kubernetes/console/templates/ingress.yaml +++ b/deploy/kubernetes/console/templates/ingress.yaml @@ -21,13 +21,16 @@ metadata: app.kubernetes.io/component: "console-ingress-tls" helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" data: - tls.crt: {{ .Values.console.service.ingress.tls.crt | default "" | b64enc | quote }} - tls.key: {{ .Values.console.service.ingress.tls.key | default "" | b64enc | quote }} +{{ template "console.generateIngressCertificate" . }} {{- end }} --- # Ingress for the Console UI service +{{- if semverCompare ">=1.16" (printf "%s.%s" .Capabilities.KubeVersion.Major .Capabilities.KubeVersion.Minor) }} +apiVersion: "networking.k8s.io/v1beta1" +{{- else }} apiVersion: "extensions/v1beta1" +{{- end }} kind: "Ingress" metadata: name: "{{ .Release.Name }}-ingress" @@ -35,6 +38,9 @@ metadata: {{- if hasKey .Values.console.service.ingress.annotations "kubernetes.io/ingress.class" | not -}} {{ $_ := set .Values.console.service.ingress.annotations "kubernetes.io/ingress.class" "nginx" }} {{- end }} + {{- if hasKey .Values.console.service.ingress.annotations "kubernetes.io/ingress.allow-http" | not -}} + {{ $_ := set .Values.console.service.ingress.annotations "kubernetes.io/ingress.allow-http" "false" }} + {{- end }} {{- if hasKey .Values.console.service.ingress.annotations "nginx.ingress.kubernetes.io/secure-backends" | not -}} {{ $_ := set .Values.console.service.ingress.annotations "nginx.ingress.kubernetes.io/secure-backends" "true" }} {{- end }} @@ -60,7 +66,7 @@ metadata: {{- end }} spec: tls: - - secretName: {{ default "{{ .Release.Name }}-ingress-tls" .Values.console.service.ingress.secretName | quote }} + - secretName: {{ default (print .Release.Name "-ingress-tls") .Values.console.service.ingress.secretName | quote }} hosts: - {{ template "ingress.host" . }} rules: diff --git a/deploy/kubernetes/console/tests/ingress_test.yaml b/deploy/kubernetes/console/tests/ingress_test.yaml index d275098c9b..50f214b502 100644 --- a/deploy/kubernetes/console/tests/ingress_test.yaml +++ b/deploy/kubernetes/console/tests/ingress_test.yaml @@ -47,10 +47,9 @@ tests: - equal: path: kind value: "Secret" - - equal: + - isNotEmpty: path: data.tls\.crt - value: "" - - equal: + - isNotEmpty: path: data.tls\.key value: "" - it: should create secret with specified values @@ -147,6 +146,7 @@ tests: - equal: path: metadata.annotations value: + kubernetes.io/ingress.allow-http: "false" kubernetes.io/ingress.class: "nginx" nginx.ingress.kubernetes.io/secure-backends: "true" nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" @@ -171,6 +171,7 @@ tests: - equal: path: metadata.annotations value: + kubernetes.io/ingress.allow-http: "false" kubernetes.io/ingress.class: "nginx" nginx.ingress.kubernetes.io/secure-backends: "true" nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" @@ -196,6 +197,7 @@ tests: path: metadata.annotations value: test-annotation: "test" + kubernetes.io/ingress.allow-http: "false" kubernetes.io/ingress.class: "nginx" nginx.ingress.kubernetes.io/secure-backends: "true" nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" diff --git a/deploy/kubernetes/console/tests/kube_version_test.yaml b/deploy/kubernetes/console/tests/kube_version_test.yaml new file mode 100644 index 0000000000..c803485484 --- /dev/null +++ b/deploy/kubernetes/console/tests/kube_version_test.yaml @@ -0,0 +1,31 @@ +suite: test stratos deployment with kube versions +templates: + - deployment.yaml +tests: + - it: should use newer API versions when kube >= 1.16 + capabilities: + kubeVersion: + major: 1 + minor: 16 + asserts: + - equal: + path: apiVersion + value: apps/v1 + - it: should use newer API versions when kube >= 2 + capabilities: + kubeVersion: + major: 2 + minor: 1 + asserts: + - equal: + path: apiVersion + value: apps/v1 + - it: should use older API versions when kube < 1.16 + capabilities: + kubeVersion: + major: 1 + minor: 14 + asserts: + - equal: + path: apiVersion + value: apps/v1beta1 diff --git a/package-lock.json b/package-lock.json index ce5b2d62f2..46c8e32a58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "stratos", - "version": "2.6.0", + "version": "2.6.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index b93e6b7991..e5e9eff2ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stratos", - "version": "2.6.0", + "version": "2.6.1", "description": "Stratos Console", "main": "index.js", "scripts": { diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell.service.ts index 4f737b86e6..c9a8089fbe 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell.service.ts @@ -1,15 +1,19 @@ import { Injectable } from '@angular/core'; +import { Store } from '@ngrx/store'; import { combineLatest, Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { map, switchMap } from 'rxjs/operators'; import { EntityServiceFactory } from '../../../../../../../core/src/core/entity-service-factory.service'; +import { CfCellHelper } from '../../../../../../../core/src/features/cloud-foundry/cf-cell.helpers'; import { MetricsConfig } from '../../../../../../../core/src/shared/components/metrics-chart/metrics-chart.component'; import { MetricsLineChartConfig } from '../../../../../../../core/src/shared/components/metrics-chart/metrics-chart.types'; import { MetricsChartHelpers, } from '../../../../../../../core/src/shared/components/metrics-chart/metrics.component.helpers'; +import { PaginationMonitorFactory } from '../../../../../../../core/src/shared/monitors/pagination-monitor.factory'; import { MetricQueryType } from '../../../../../../../core/src/shared/services/metrics-range-selector.types'; import { MetricQueryConfig } from '../../../../../../../store/src/actions/metrics.actions'; +import { AppState } from '../../../../../../../store/src/app-state'; import { IMetricMatrixResult, IMetrics, IMetricVectorResult } from '../../../../../../../store/src/types/base-metric.types'; import { IMetricCell } from '../../../../../../../store/src/types/metric.types'; import { FetchCFCellMetricsAction } from '../../../../../actions/cf-metrics.actions'; @@ -17,7 +21,14 @@ import { ActiveRouteCfCell } from '../../../cf-page.types'; export const enum CellMetrics { - HEALTHY = 'firehose_value_metric_rep_unhealthy_cell', + /** + * Deprecated since Diego v2.31.0. See https://github.com/bosh-prometheus/prometheus-boshrelease/issues/333 + */ + HEALTHY_DEP = 'firehose_value_metric_rep_unhealthy_cell', + /** + * Available from Diego v2.31.0. See https://github.com/bosh-prometheus/prometheus-boshrelease/issues/333 + */ + HEALTHY = 'firehose_value_metric_rep_garden_health_check_failed', REMAINING_CONTAINERS = 'firehose_value_metric_rep_capacity_remaining_containers', REMAINING_DISK = 'firehose_value_metric_rep_capacity_remaining_disk', REMAINING_MEMORY = 'firehose_value_metric_rep_capacity_remaining_memory', @@ -27,6 +38,10 @@ export const enum CellMetrics { CPUS = 'firehose_value_metric_rep_num_cpus' } + +/** + * Designed to be used once drilled down to a cell (see ActiveRouteCfCell) + */ @Injectable() export class CloudFoundryCellService { @@ -52,12 +67,13 @@ export class CloudFoundryCellService { constructor( activeRouteCfCell: ActiveRouteCfCell, - private entityServiceFactory: EntityServiceFactory) { + private entityServiceFactory: EntityServiceFactory, + store: Store, + paginationMonitorFactory: PaginationMonitorFactory) { this.cellId = activeRouteCfCell.cellId; this.cfGuid = activeRouteCfCell.cfGuid; - this.healthy$ = this.generate(CellMetrics.HEALTHY); this.remainingContainers$ = this.generate(CellMetrics.REMAINING_CONTAINERS); this.totalContainers$ = this.generate(CellMetrics.TOTAL_CONTAINERS); this.remainingDisk$ = this.generate(CellMetrics.REMAINING_DISK); @@ -70,8 +86,19 @@ export class CloudFoundryCellService { this.usageDisk$ = this.generateUsage(this.remainingDisk$, this.totalDisk$); this.usageMemory$ = this.generateUsage(this.remainingMemory$, this.totalMemory$); - this.cellMetric$ = this.generate(CellMetrics.HEALTHY, true); - + const cellHelper = new CfCellHelper(store, paginationMonitorFactory); + const action$ = cellHelper.createCellMetricAction(this.cfGuid); + this.cellMetric$ = action$.pipe( + switchMap(action => { + this.healthyMetricId = action.guid; + return this.generate(action.query.metric as CellMetrics, true); + }) + ); + this.healthy$ = action$.pipe( + switchMap(action => { + return this.generate(action.query.metric as CellMetrics, false); + }) + ); } public buildMetricConfig( @@ -99,17 +126,14 @@ export class CloudFoundryCellService { return lineChartConfig; } - private generate(metric: CellMetrics, isMetric = false): Observable { - const action = new FetchCFCellMetricsAction( + private generate(metric: CellMetrics, isMetric = false, customAction?: FetchCFCellMetricsAction): Observable { + const action = customAction || new FetchCFCellMetricsAction( this.cfGuid, this.cellId, new MetricQueryConfig(metric + `{bosh_job_id="${this.cellId}"}`, {}), MetricQueryType.QUERY, false ); - if (metric === CellMetrics.HEALTHY) { - this.healthyMetricId = action.guid; - } return this.entityServiceFactory.create>>( action.guid, action, diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cells.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cells.component.ts index dd2e028c36..dd9fb19aa9 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cells.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cells.component.ts @@ -1,7 +1,11 @@ import { Component } from '@angular/core'; +import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; +import { CfCellHelper } from '../../../../../../core/src/features/cloud-foundry/cf-cell.helpers'; import { ListConfig } from '../../../../../../core/src/shared/components/list/list.component.types'; +import { PaginationMonitorFactory } from '../../../../../../core/src/shared/monitors/pagination-monitor.factory'; +import { AppState } from '../../../../../../store/src/app-state'; import { CfCellsListConfigService, } from '../../../../shared/components/list/list-types/cf-cells/cf-cells-list-config.service'; @@ -23,7 +27,12 @@ import { CloudFoundryEndpointService } from '../../services/cloud-foundry-endpoi export class CloudFoundryCellsComponent { hasCellMetrics$: Observable; - constructor(cfEndpointService: CloudFoundryEndpointService) { - this.hasCellMetrics$ = cfEndpointService.hasCellMetrics(cfEndpointService.cfGuid); + constructor( + cfEndpointService: CloudFoundryEndpointService, + store: Store, + paginationMonitorFactory: PaginationMonitorFactory + ) { + const cellHelper = new CfCellHelper(store, paginationMonitorFactory); + this.hasCellMetrics$ = cellHelper.hasCellMetrics(cfEndpointService.cfGuid); } } diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-instance/cf-app-instances-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-instance/cf-app-instances-config.service.ts index 4e0b40a398..31d25e1a8d 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-instance/cf-app-instances-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-instance/cf-app-instances-config.service.ts @@ -2,17 +2,14 @@ import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; -import { combineLatest, filter, map, switchMap } from 'rxjs/operators'; +import { combineLatest, map, switchMap } from 'rxjs/operators'; import { DeleteApplicationInstance } from '../../../../../../../cloud-foundry/src/actions/application.actions'; import { FetchApplicationMetricsAction } from '../../../../../../../cloud-foundry/src/actions/cf-metrics.actions'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; -import { ApplicationService } from '../../../../../../../cloud-foundry/src/features/applications/application.service'; -import { - CloudFoundryEndpointService, -} from '../../../../../../../cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service'; import { EntityServiceFactory } from '../../../../../../../core/src/core/entity-service-factory.service'; import { UtilsService } from '../../../../../../../core/src/core/utils.service'; +import { CfCellHelper } from '../../../../../../../core/src/features/cloud-foundry/cf-cell.helpers'; import { ConfirmationDialogConfig } from '../../../../../../../core/src/shared/components/confirmation-dialog.config'; import { ConfirmationDialogService } from '../../../../../../../core/src/shared/components/confirmation-dialog.service'; import { @@ -24,10 +21,12 @@ import { IListConfig, ListViewTypes, } from '../../../../../../../core/src/shared/components/list/list.component.types'; +import { PaginationMonitorFactory } from '../../../../../../../core/src/shared/monitors/pagination-monitor.factory'; import { MetricQueryType } from '../../../../../../../core/src/shared/services/metrics-range-selector.types'; import { MetricQueryConfig } from '../../../../../../../store/src/actions/metrics.actions'; import { IMetricMatrixResult, IMetrics } from '../../../../../../../store/src/types/base-metric.types'; import { IMetricApplication } from '../../../../../../../store/src/types/metric.types'; +import { ApplicationService } from '../../../../../features/applications/application.service'; import { ListAppInstance } from './app-instance-types'; import { CfAppInstancesDataSource } from './cf-app-instances-data-source'; import { TableCellCfCellComponent } from './table-cell-cf-cell/table-cell-cf-cell.component'; @@ -199,10 +198,11 @@ export class CfAppInstancesConfigService implements IListConfig private router: Router, private confirmDialog: ConfirmationDialogService, entityServiceFactory: EntityServiceFactory, - cfEndpointService: CloudFoundryEndpointService + paginationMonitorFactory: PaginationMonitorFactory ) { + const cellHelper = new CfCellHelper(store, paginationMonitorFactory); - this.initialised$ = cfEndpointService.hasCellMetrics(appService.cfGuid).pipe( + this.initialised$ = cellHelper.hasCellMetrics(appService.cfGuid).pipe( map(hasMetrics => { if (hasMetrics) { this.columns.splice(1, 0, this.cfCellColumn); diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/base-cf/base-cf-list-config.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/base-cf/base-cf-list-config.ts index d92a018ff8..dbd2be20a1 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/base-cf/base-cf-list-config.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/base-cf/base-cf-list-config.ts @@ -1,3 +1,5 @@ +import { of } from 'rxjs'; + import { IListDataSource, } from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source-types'; @@ -19,4 +21,5 @@ export class BaseCfListConfig implements IListConfig { getMultiActions = () => []; getSingleActions = () => []; getMultiFiltersConfigs = () => []; + getInitialised = () => of(true); } diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cell-health/cf-cell-health-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cell-health/cf-cell-health-list-config.service.ts index e01675f771..4c62959ca5 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cell-health/cf-cell-health-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cell-health/cf-cell-health-list-config.service.ts @@ -2,8 +2,10 @@ import { DatePipe } from '@angular/common'; import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; +import { Observable } from 'rxjs'; +import { first, tap } from 'rxjs/operators'; -import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; +import { CfCellHelper } from '../../../../../../../core/src/features/cloud-foundry/cf-cell.helpers'; import { BooleanIndicatorType, } from '../../../../../../../core/src/shared/components/boolean-indicator/boolean-indicator.component'; @@ -13,10 +15,10 @@ import { } from '../../../../../../../core/src/shared/components/list/list-table/table-cell-boolean-indicator/table-cell-boolean-indicator.component'; import { ITableColumn } from '../../../../../../../core/src/shared/components/list/list-table/table.types'; import { ListViewTypes } from '../../../../../../../core/src/shared/components/list/list.component.types'; -import { MetricQueryType } from '../../../../../../../core/src/shared/services/metrics-range-selector.types'; +import { PaginationMonitorFactory } from '../../../../../../../core/src/shared/monitors/pagination-monitor.factory'; import { ListView } from '../../../../../../../store/src/actions/list.actions'; -import { MetricQueryConfig } from '../../../../../../../store/src/actions/metrics.actions'; import { FetchCFCellMetricsPaginatedAction } from '../../../../../actions/cf-metrics.actions'; +import { CFAppState } from '../../../../../cf-app-state'; import { CloudFoundryCellService, } from '../../../../../features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell.service'; @@ -37,6 +39,7 @@ export class CfCellHealthListConfigService extends BaseCfListConfig; private boolIndicatorConfig: TableCellBooleanIndicatorComponentConfig = { isEnabled: (row: CfCellHealthEntry) => @@ -46,25 +49,28 @@ export class CfCellHealthListConfigService extends BaseCfListConfig, cloudFoundryCellService: CloudFoundryCellService, private datePipe: DatePipe) { + constructor( + private store: Store, + cloudFoundryCellService: CloudFoundryCellService, + private datePipe: DatePipe, + private paginationMonitorFactory: PaginationMonitorFactory) { super(); - const action = this.createMetricsAction(cloudFoundryCellService.cfGuid, cloudFoundryCellService.cellId); - this.dataSource = new CfCellHealthDataSource(store, this, action); + + this.init$ = this.createMetricsAction(cloudFoundryCellService.cfGuid, cloudFoundryCellService.cellId).pipe( + first(), + tap(action => { + this.dataSource = new CfCellHealthDataSource(this.store, this, action); + }) + ); this.showCustomTime = true; } - private createMetricsAction(cfGuid: string, cellId: string): FetchCFCellMetricsPaginatedAction { - const action = new FetchCFCellMetricsPaginatedAction( - cfGuid, - cellId, - new MetricQueryConfig(`firehose_value_metric_rep_unhealthy_cell{bosh_job_id="${cellId}"}`, {}), - MetricQueryType.QUERY - ); - action.initialParams['order-direction-field'] = 'dateTime'; - action.initialParams['order-direction'] = 'asc'; - return action; + private createMetricsAction(cfGuid: string, cellId: string): Observable { + const cellHelper = new CfCellHelper(this.store, this.paginationMonitorFactory); + return cellHelper.createCellMetricAction(cfGuid, cellId); } + getInitialised = () => this.init$; getColumns = (): ITableColumn[] => [ { columnId: 'dateTime', diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cells/cf-cells-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cells/cf-cells-data-source.ts index cf7e8e6512..44190871bf 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cells/cf-cells-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cells/cf-cells-data-source.ts @@ -1,20 +1,17 @@ import { Store } from '@ngrx/store'; import { map } from 'rxjs/operators'; -import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { ListDataSource, } from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; import { IListConfig } from '../../../../../../../core/src/shared/components/list/list.component.types'; -import { MetricQueryType } from '../../../../../../../core/src/shared/services/metrics-range-selector.types'; -import { MetricQueryConfig } from '../../../../../../../store/src/actions/metrics.actions'; import { IMetrics, IMetricVectorResult } from '../../../../../../../store/src/types/base-metric.types'; import { IMetricCell } from '../../../../../../../store/src/types/metric.types'; import { FetchCFCellMetricsPaginatedAction } from '../../../../../actions/cf-metrics.actions'; +import { CFAppState } from '../../../../../cf-app-state'; import { cfEntityFactory } from '../../../../../cf-entity-factory'; // TODO: Move file to CF package (#3769) - export class CfCellsDataSource extends ListDataSource, IMetrics>> { @@ -23,14 +20,11 @@ export class CfCellsDataSource static cellHealthyPath = 'value.1'; static cellDeploymentPath = 'metric.bosh_deployment'; - constructor(store: Store, cfGuid: string, listConfig: IListConfig>) { - const action = new FetchCFCellMetricsPaginatedAction( - cfGuid, - cfGuid, - new MetricQueryConfig('firehose_value_metric_rep_unhealthy_cell', {}), - MetricQueryType.QUERY - ); - + constructor( + store: Store, + listConfig: IListConfig>, + action: FetchCFCellMetricsPaginatedAction + ) { super({ store, action, diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cells/cf-cells-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cells/cf-cells-list-config.service.ts index bb03b0907e..6e664f59e2 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cells/cf-cells-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cells/cf-cells-list-config.service.ts @@ -1,8 +1,11 @@ // tslint:disable:max-line-length import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; +import { Observable } from 'rxjs'; +import { first, tap } from 'rxjs/operators'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; +import { CfCellHelper } from '../../../../../../../core/src/features/cloud-foundry/cf-cell.helpers'; import { BooleanIndicatorType, } from '../../../../../../../core/src/shared/components/boolean-indicator/boolean-indicator.component'; @@ -12,6 +15,7 @@ import { } from '../../../../../../../core/src/shared/components/list/list-table/table-cell-boolean-indicator/table-cell-boolean-indicator.component'; import { ITableColumn } from '../../../../../../../core/src/shared/components/list/list-table/table.types'; import { ListViewTypes } from '../../../../../../../core/src/shared/components/list/list.component.types'; +import { PaginationMonitorFactory } from '../../../../../../../core/src/shared/monitors/pagination-monitor.factory'; import { ListView } from '../../../../../../../store/src/actions/list.actions'; import { IMetricVectorResult } from '../../../../../../../store/src/types/base-metric.types'; import { IMetricCell } from '../../../../../../../store/src/types/metric.types'; @@ -33,6 +37,7 @@ export class CfCellsListConfigService extends BaseCfListConfig; private boolIndicatorConfig: TableCellBooleanIndicatorComponentConfig> = { // "0 signifies healthy, and 1 signifies unhealthy" @@ -98,11 +103,21 @@ export class CfCellsListConfigService extends BaseCfListConfig, private activeRouteCfCell: ActiveRouteCfCell) { + constructor( + store: Store, + private activeRouteCfCell: ActiveRouteCfCell, + paginationMonitorFactory: PaginationMonitorFactory) { super(); - this.dataSource = new CfCellsDataSource(store, activeRouteCfCell.cfGuid, this); + const cellHelper = new CfCellHelper(store, paginationMonitorFactory); + this.init$ = cellHelper.createCellMetricAction(activeRouteCfCell.cfGuid).pipe( + first(), + tap(action => { + this.dataSource = new CfCellsDataSource(store, this, action); + }) + ); } + getInitialised = () => this.init$; getColumns = () => this.columns; getDataSource = () => this.dataSource; } diff --git a/src/frontend/packages/core/src/core/endpoints.service.ts b/src/frontend/packages/core/src/core/endpoints.service.ts index 1195cdeb90..7a887277c1 100644 --- a/src/frontend/packages/core/src/core/endpoints.service.ts +++ b/src/frontend/packages/core/src/core/endpoints.service.ts @@ -2,18 +2,15 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router'; import { Store } from '@ngrx/store'; import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; -import { filter, first, map, skipWhile, withLatestFrom } from 'rxjs/operators'; +import { first, map, skipWhile, withLatestFrom } from 'rxjs/operators'; import { RouterNav } from '../../../store/src/actions/router.actions'; import { EndpointOnlyAppState, IRequestEntityTypeState } from '../../../store/src/app-state'; import { AuthState } from '../../../store/src/reducers/auth.reducer'; -import { - endpointEntitiesSelector, - endpointsEntityRequestDataSelector, - endpointStatusSelector, -} from '../../../store/src/selectors/endpoint.selectors'; +import { endpointEntitiesSelector, endpointStatusSelector } from '../../../store/src/selectors/endpoint.selectors'; import { EndpointModel, EndpointState } from '../../../store/src/types/endpoint.types'; import { EndpointHealthCheck, EndpointHealthChecks } from '../../endpoints-health-checks'; +import { endpointHasMetricsByAvailable } from '../features/endpoints/endpoint-helpers'; import { entityCatalogue } from './entity-catalogue/entity-catalogue.service'; import { UserService } from './user.service'; @@ -121,11 +118,7 @@ export class EndpointsService implements CanActivate { } hasMetrics(endpointId: string): Observable { - return this.store.select(endpointsEntityRequestDataSelector(endpointId)).pipe( - filter(endpoint => !!endpoint), - map(endpoint => endpoint.metricsAvailable), - first() - ); + return endpointHasMetricsByAvailable(this.store, endpointId); } doesNotHaveConnectedEndpointType(type: string): Observable { diff --git a/src/frontend/packages/core/src/features/cloud-foundry/cf-cell.helpers.ts b/src/frontend/packages/core/src/features/cloud-foundry/cf-cell.helpers.ts new file mode 100644 index 0000000000..af49499bbf --- /dev/null +++ b/src/frontend/packages/core/src/features/cloud-foundry/cf-cell.helpers.ts @@ -0,0 +1,71 @@ +import { Store } from '@ngrx/store'; +import { Observable, of } from 'rxjs'; +import { filter, first, map, publishReplay, refCount, switchMap } from 'rxjs/operators'; + +import { CFEntityConfig } from '../../../../cloud-foundry/cf-types'; +import { FetchCFCellMetricsPaginatedAction } from '../../../../cloud-foundry/src/actions/cf-metrics.actions'; +import { + CellMetrics, +} from '../../../../cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell.service'; +import { MetricQueryConfig } from '../../../../store/src/actions/metrics.actions'; +import { AppState } from '../../../../store/src/app-state'; +import { getPaginationObservables } from '../../../../store/src/reducers/pagination-reducer/pagination-reducer.helper'; +import { IMetrics } from '../../../../store/src/types/base-metric.types'; +import { PaginationMonitorFactory } from '../../shared/monitors/pagination-monitor.factory'; +import { MetricQueryType } from '../../shared/services/metrics-range-selector.types'; +import { endpointHasMetricsByAvailable } from '../endpoints/endpoint-helpers'; + +export class CfCellHelper { + + constructor( + private store: Store, + private paginationMonitorFactory: PaginationMonitorFactory) { + } + + public createCellMetricAction(cfId: string, cellId?: string): Observable { + const cellIdString = !!cellId ? `{bosh_job_id="${cellId}"}` : ''; + + const newMetricAction: FetchCFCellMetricsPaginatedAction = new FetchCFCellMetricsPaginatedAction( + cfId, + cfId, + new MetricQueryConfig(CellMetrics.HEALTHY + cellIdString, {}), + MetricQueryType.QUERY + ); + return this.hasMetric(newMetricAction).pipe( + switchMap(hasNewMetric => hasNewMetric ? + of(hasNewMetric) : + this.hasMetric(new FetchCFCellMetricsPaginatedAction( + cfId, + cfId, + new MetricQueryConfig(CellMetrics.HEALTHY_DEP + cellIdString, {}), + MetricQueryType.QUERY + )) + ) + ); + } + + private hasMetric(action: FetchCFCellMetricsPaginatedAction): Observable { + return getPaginationObservables({ + store: this.store, + action, + paginationMonitor: this.paginationMonitorFactory.create( + action.paginationKey, + new CFEntityConfig(action.entityType) + ) + }).entities$.pipe( + filter(entities => !!entities && !!entities.length), + first(), + map(entities => !!entities.find(entity => !!entity.data.result.length) ? action : null), + publishReplay(1), + refCount() + ); + } + + public hasCellMetrics(endpointId: string): Observable { + return endpointHasMetricsByAvailable(this.store, endpointId).pipe( + // If metrics set up for this endpoint check if we can fetch cell metrics from it. + // If the metric is unknown an empty list is returned + switchMap(hasMetrics => hasMetrics ? this.createCellMetricAction(endpointId).pipe(map(action => !!action)) : of(false)) + ); + } +} diff --git a/src/frontend/packages/core/src/features/endpoints/endpoint-helpers.ts b/src/frontend/packages/core/src/features/endpoints/endpoint-helpers.ts index 2b83b6b969..0d6a5a43ae 100644 --- a/src/frontend/packages/core/src/features/endpoints/endpoint-helpers.ts +++ b/src/frontend/packages/core/src/features/endpoints/endpoint-helpers.ts @@ -1,10 +1,13 @@ import { Type } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; -import { first, map } from 'rxjs/operators'; +import { filter, first, map } from 'rxjs/operators'; -import { EndpointOnlyAppState } from '../../../../store/src/app-state'; -import { endpointEntitiesSelector } from '../../../../store/src/selectors/endpoint.selectors'; +import { AppState, EndpointOnlyAppState } from '../../../../store/src/app-state'; +import { + endpointEntitiesSelector, + endpointsEntityRequestDataSelector, +} from '../../../../store/src/selectors/endpoint.selectors'; import { EndpointModel } from '../../../../store/src/types/endpoint.types'; import { EndpointListDetailsComponent } from '../../shared/components/list/list-types/endpoint/endpoint-list.helpers'; @@ -39,3 +42,12 @@ export function endpointHasMetrics(endpointGuid: string, store: Store !!state[endpointGuid].metadata && !!state[endpointGuid].metadata.metrics) ); } + +// There are two different methods for checking if an endpoint has metrics. Need to understand use cases +export function endpointHasMetricsByAvailable(store: Store, endpointId: string): Observable { + return store.select(endpointsEntityRequestDataSelector(endpointId)).pipe( + filter(endpoint => !!endpoint), + map(endpoint => endpoint.metricsAvailable), + first() + ); +} diff --git a/src/jetstream/plugins/metrics/cloud_foundry.go b/src/jetstream/plugins/metrics/cloud_foundry.go index f7fa8c8e84..7fd1e6e1e2 100644 --- a/src/jetstream/plugins/metrics/cloud_foundry.go +++ b/src/jetstream/plugins/metrics/cloud_foundry.go @@ -15,6 +15,7 @@ import ( var ( cellQueryWhiteList = []string{ "firehose_value_metric_rep_unhealthy_cell", + "firehose_value_metric_rep_garden_health_check_failed", "firehose_value_metric_rep_capacity_remaining_containers", "firehose_value_metric_rep_capacity_remaining_disk", "firehose_value_metric_rep_capacity_remaining_memory", @@ -205,6 +206,5 @@ func (m *MetricsSpecification) getCloudFoundryCellMetrics(c echo.Context) error } cnsiList := strings.Split(c.Request().Header.Get("x-cap-cnsi-list"), ",") - return m.makePrometheusRequest(c, cnsiList, "") } diff --git a/src/jetstream/repository/console_config/env_lookup.go b/src/jetstream/repository/console_config/env_lookup.go index 8c87844b34..d5d3997200 100644 --- a/src/jetstream/repository/console_config/env_lookup.go +++ b/src/jetstream/repository/console_config/env_lookup.go @@ -104,8 +104,14 @@ func MigrateSetupData(portal interfaces.PortalProxy, configStore Repository) err return err } - if err := migrateConfigSetting(portal.Env(), configStore, "SSO_LOGIN", strconv.FormatBool(config.UseSSO), "false"); err != nil { - return err + // Don't store previous SSO_LOGIN value if it's false. + // SSO_LOGIN was incorrectly being set in previous console config table, this was then transferred over here where the console expects + // previous values to have been explicitly set by user (and as such should take precedents over env vars) + // See https://github.com/cloudfoundry/stratos/issues/4013 + if config.UseSSO == true { + if err := migrateConfigSetting(portal.Env(), configStore, "SSO_LOGIN", strconv.FormatBool(config.UseSSO), "false"); err != nil { + return err + } } // Delete the content form the legacy table From cb91516dd40c574f322f93a134fe2a336da610d8 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Wed, 4 Dec 2019 20:28:36 +0000 Subject: [PATCH 25/29] Add a dark mode (#4017) * WIP Dark theme * Add toggle, fix some of the theme glitches * WIP * WIP * Fixes * More fixes * Final tweaks * Tidying up * Fix tests * Conditionally support OS theme selection * Fix acme theme * Tweaks following review with RC * Final tweaks following review - wired in dark-theme-supported to theme options - removed test line from custom.scss --- docs/customizing.md | 20 ++- examples/custom-src/frontend/sass/custom.scss | 8 +- .../custom-src/frontend/sass/custom/acme.scss | 2 +- package.json | 1 + ...utoscaler-metric-chart-card.component.scss | 1 - .../actions/deploy-applications.actions.ts | 2 +- .../deploy-application.component.theme.scss | 12 +- .../packages/core/misc/custom/custom.scss | 1 - .../packages/core/sass/_all-theme.scss | 24 ++- .../sass/components/mat-snack-bar.theme.scss | 20 +++ .../core/sass/components/mat-tabs.theme.scss | 20 --- .../components/ngx-charts-gauge.theme.scss | 6 + src/frontend/packages/core/sass/theme.scss | 44 +++-- .../packages/core/src/app.component.html | 7 +- .../packages/core/src/app.component.ts | 4 +- .../packages/core/src/core/style.service.ts | 38 +++++ .../packages/core/src/core/theme.service.ts | 151 ++++++++++++++++++ .../dashboard-base.component.scss | 1 - .../page-side-nav.component.theme.scss | 2 +- .../profile-info/profile-info.component.html | 12 ++ .../profile-info/profile-info.component.ts | 7 +- .../code-block.component.theme.scss | 18 ++- .../meta-card-item.component.scss | 4 - .../meta-card-item.component.theme.scss | 12 ++ .../components/list/list.component.theme.scss | 11 +- .../page-header.component.theme.scss | 7 + .../packages/core/src/shared/shared.module.ts | 8 +- src/frontend/packages/core/src/styles.scss | 8 + .../core/test-framework/store-test-helper.ts | 3 +- .../store/src/actions/dashboard-actions.ts | 6 + .../store/src/effects/dashboard.effects.ts | 24 +++ .../store/src/reducers/dashboard-reducer.ts | 10 ++ .../packages/store/src/store.module.ts | 4 +- 33 files changed, 423 insertions(+), 75 deletions(-) create mode 100644 src/frontend/packages/core/sass/components/mat-snack-bar.theme.scss delete mode 100644 src/frontend/packages/core/sass/components/mat-tabs.theme.scss create mode 100644 src/frontend/packages/core/sass/components/ngx-charts-gauge.theme.scss create mode 100644 src/frontend/packages/core/src/core/style.service.ts create mode 100644 src/frontend/packages/core/src/core/theme.service.ts create mode 100644 src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.theme.scss create mode 100644 src/frontend/packages/store/src/effects/dashboard.effects.ts diff --git a/docs/customizing.md b/docs/customizing.md index 2171212e3c..c167a77516 100644 --- a/docs/customizing.md +++ b/docs/customizing.md @@ -50,7 +50,7 @@ In this file you can set any or all of the following variables: |---|---| |$stratos-theme|The main theme to use for Stratos| |$stratos-nav-theme|Theme to use for the side navigation panel| -$stratos-status-theme|Theme to use for displaying status in Stratos| +|$stratos-status-theme|Theme to use for displaying status in Stratos| Note that you do not have to specify all of these - defaults will be used if they are not set. @@ -72,6 +72,24 @@ $suse-app-theme: mat-light-theme($suse-app-primary, $suse-app-primary, $suse-the $stratos-theme: $suse-app-theme; ``` +#### Creating or disabling the Dark theme + +You can also change the Dark theme, if you wish, by defining the following variables: + +|Variable|Purpose| +|---|---| +|$stratos-dark-theme|The dark theme to use for Stratos| +|$stratos-dark-nav-theme|Dark theme to use for the side navigation panel| +|$stratos-dark-status-theme|Dark theme to use for displaying status in Stratos| + +Note that minimally you must supply `stratos-dark-theme` to create a dark theme. + +By default a dark theme is assumed to be available and the default will be used if not overridden. You can disable dark theme support in the UI by setting the following variable in your `custom.scss`: + +``` +$stratos-dark-theme-supported: false; +``` + ### Changing Styles We don't generally recommend modifying styles, since from version to version of Stratos, we may change the styles used slightly which can mean any modifications you made will need updating. Should you wish to do so, you can modify these in the same `custom.scss` file that is used for theming. diff --git a/examples/custom-src/frontend/sass/custom.scss b/examples/custom-src/frontend/sass/custom.scss index 308b1ef362..e3eb84c38a 100644 --- a/examples/custom-src/frontend/sass/custom.scss +++ b/examples/custom-src/frontend/sass/custom.scss @@ -2,11 +2,17 @@ // ACME Primary Material Design pallette // From http://mcg.mbitson.com/#!?mcgpalette0=%233f51b5 -$acme-primary: (50: #e0f7f0, 100: #b3ecd9, 200: #80e0c0, 300: #4dd3a7, 400: #26c994, 500: #00c081, 600: #00ba79, 700: #00b26e, 800: #00aa64, 900: #009c51, A100: #c7ffe0, A200: #94ffc4, A400: #61ffa8, A700: #47ff9a, contrast: (50: #000, 100: #000, 200: #000, 300: #000, 400: #000, 500: #fff, 600: #fff, 700: #fff, 800: #fff, 900: #fff, A100: #000, A200: #000, A400: #000, A700: #000)); +$acme-primary: (50: #e8eaf6, 100: #c5cbe9, 200: #9fa8da, 300: #7985cb, 400: #5c6bc0, 500: #3f51b5, 600: #394aae, 700: #3140a5, 800: #29379d, 900: #1b278d, A100: #c6cbff, A200: #939dff, A400: #606eff, A700: #4757ff, contrast: ( 50: #000000, 100: #000000, 200: #000000, 300: #000000, 400: #ffffff, 500: #ffffff, 600: #ffffff, 700: #ffffff, 800: #ffffff, 900: #ffffff, A100: #000000, A200: #000000, A400: #ffffff, A700: #ffffff, )); $mat-red: ( 50: #ffebee, 100: #ffcdd2, 200: #ef9a9a, 300: #e57373, 400: #ef5350, 500: #f44336, 600: #e53935, 700: #d32f2f, 800: #c62828, 900: #b71c1c, A100: #ff8a80, A200: #ff5252, A400: #ff1744, A700: #d50000, contrast: ( 50: $black-87-opacity, 100: $black-87-opacity, 200: $black-87-opacity, 300: $black-87-opacity, 400: $black-87-opacity, 500: white, 600: white, 700: white, 800: $white-87-opacity, 900: $white-87-opacity, A100: $black-87-opacity, A200: white, A400: white, A700: white, )); +// Common $acme-theme-primary: mat-palette($acme-primary); $acme-theme-warn: mat-palette($mat-red); + +// Dark Theme +$stratos-dark-theme: mat-dark-theme($acme-theme-primary, $acme-theme-primary, $acme-theme-warn); + +// Default Theme $stratos-theme: mat-light-theme($acme-theme-primary, $acme-theme-primary, $acme-theme-warn); @import 'custom/acme'; diff --git a/examples/custom-src/frontend/sass/custom/acme.scss b/examples/custom-src/frontend/sass/custom/acme.scss index d153c4bc36..75794ebb6e 100644 --- a/examples/custom-src/frontend/sass/custom/acme.scss +++ b/examples/custom-src/frontend/sass/custom/acme.scss @@ -11,7 +11,7 @@ $acme-blue: #073155; $acme-side-nav: $acme-secondary; $acme-side-nav-active: #003358; -.stratos { +body.stratos { app-page-subheader { .page-subheader { background-color: #fff; diff --git a/package.json b/package.json index e5e9eff2ad..acaac983a2 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "ng": "ng", "start": "npm run customize && ng serve", "start-high-mem": "npm run customize && node --max_old_space_size=8192 node_modules/@angular/cli/bin/ng serve", + "start-dev-high-mem": "npm run customize && node --max_old_space_size=8192 node_modules/@angular/cli/bin/ng serve --aot=false", "start-prod-high-mem": "npm run customize && node --max_old_space_size=8192 node_modules/@angular/cli/bin/ng serve --prod", "start-dev": "ng serve --aot=false", "test": "run-s test-frontend:* --continue-on-error", diff --git a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-card/app-autoscaler-metric-chart-card.component.scss b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-card/app-autoscaler-metric-chart-card.component.scss index 002bddcdbd..de27bc5c5e 100644 --- a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-card/app-autoscaler-metric-chart-card.component.scss +++ b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-card/app-autoscaler-metric-chart-card.component.scss @@ -1,6 +1,5 @@ mat-card-header { .autoscaler-metric-subtitle { - color: rgba(0, 0, 0, .54); font-size: .9em; margin-top: .3em; } diff --git a/src/frontend/packages/cloud-foundry/src/actions/deploy-applications.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/deploy-applications.actions.ts index 7264dee6a7..36baef9f80 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/deploy-applications.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/deploy-applications.actions.ts @@ -9,7 +9,7 @@ import { GitAppDetails, OverrideAppDetails, SourceType } from '../store/types/de import { GitBranch, GitCommit } from '../store/types/git.types'; export const SET_APP_SOURCE_DETAILS = '[Deploy App] Application Source'; -export const CHECK_PROJECT_EXISTS = '[Deploy App] Check Projet exists'; +export const CHECK_PROJECT_EXISTS = '[Deploy App] Check Project exists'; export const PROJECT_DOESNT_EXIST = '[Deploy App] Project Doesn\'t exist'; export const PROJECT_FETCH_FAILED = '[Deploy App] Project Fetch Failed'; export const PROJECT_EXISTS = '[Deploy App] Project exists'; diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application.component.theme.scss b/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application.component.theme.scss index 0ce40249ca..d5f3293582 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application.component.theme.scss +++ b/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application.component.theme.scss @@ -1,7 +1,17 @@ @import '~@angular/material/theming'; @mixin app-deploy-app-theme($theme, $app-theme) { + $is-dark: map-get($theme, is-dark); + $ansi-colors: map-get($app-theme, ansi-colors); + $background-color: map-get(map-get($ansi-colors, 'white'), intense); + + @if $is-dark == true { + $background-colors: map-get($theme, background); + $foreground-colors: map-get($theme, foreground); + $background-color: lighten(mat-color($background-colors, background), 5%); + } + .deploy-app { - background-color: map-get(map-get($ansi-colors, 'white'), intense); + background-color: $background-color; } } diff --git a/src/frontend/packages/core/misc/custom/custom.scss b/src/frontend/packages/core/misc/custom/custom.scss index eb5d596b7e..f6d83b743c 100644 --- a/src/frontend/packages/core/misc/custom/custom.scss +++ b/src/frontend/packages/core/misc/custom/custom.scss @@ -3,4 +3,3 @@ // This file is in the .gitignore - changes will not be flagged // The customization build step will replace this file with the custom one if provided - diff --git a/src/frontend/packages/core/sass/_all-theme.scss b/src/frontend/packages/core/sass/_all-theme.scss index 184dc594e9..924e13afcf 100644 --- a/src/frontend/packages/core/sass/_all-theme.scss +++ b/src/frontend/packages/core/sass/_all-theme.scss @@ -32,6 +32,7 @@ @import '../src/shared/components/app-action-monitor-icon/app-action-monitor-icon.component.theme'; @import '../src/shared/components/upload-progress-indicator/upload-progress-indicator.component.theme'; @import '../src/shared/components/list/list-cards/meta-card/meta-card-base/meta-card.component.theme'; +@import '../src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.theme'; @import '../src/shared/components/start-end-date/start-end-date.component.theme'; @import '../src/shared/components/metrics-chart/metrics-chart.component.theme'; @import '../src/shared/components/metrics-range-selector/metrics-range-selector.component.theme'; @@ -41,7 +42,8 @@ @import '../src/features/user-profile/profile-info/profile-info.component.theme'; @import '../src/core/stateful-icon/stateful-icon.component.theme'; @import '../src/shared/components/markdown-preview/markdown-preview.component.theme'; -@import './components/mat-tabs.theme'; +@import './components/mat-snack-bar.theme'; +@import './components/ngx-charts-gauge.theme'; @import './components/text-status.theme'; @import './components/hyperlinks.theme'; @import './mat-themes'; @@ -82,12 +84,10 @@ $side-nav-light-active: #484848; $warn: map-get($theme, warn); $subdued: mat-contrast($primary, 50); - @if $is-dark==true { + @if $is-dark == true { $app-background-color: lighten(mat-color($background-colors, background), 10%); - $subdued: darken($subdued, 50); - } - - @else { + $subdued: lighten($subdued, 90); + } @else { $app-background-color: darken(mat-color($background-colors, background), 2%); $subdued: lighten($subdued, 50); } @@ -106,7 +106,8 @@ $side-nav-light-active: #484848; @include steppers-theme($theme, $app-theme); @include list-theme($theme, $app-theme); @include app-base-page-theme($theme, $app-theme); - @include app-mat-tabs-theme($theme, $app-theme); + @include app-mat-snack-bar-theme($theme, $app-theme); + @include ngx-charts-gauge($theme, $app-theme); @include app-text-status-theme($theme, $app-theme); @include app-card-status-theme($theme, $app-theme); @include app-usage-gauge-theme($theme, $app-theme); @@ -149,15 +150,14 @@ $side-nav-light-active: #484848; @include page-side-nav-theme($theme, $app-theme); @include cf-admin-add-user-warning($theme, $app-theme); @include entity-summary-title-theme($theme, $app-theme); + @include app-meta-card-item-theme($theme, $app-theme); @include error-page-theme($theme, $app-theme); } @function app-generate-nav-theme($theme, $nav-theme: null) { @if ($nav-theme) { @return $nav-theme; - } - - @else { + } @else { // Use default palette for side navigation @return (background: $side-nav-light-bg, background-top: $side-nav-light-bg, text: darken($side-nav-light-text, 10%), active: $side-nav-light-active, active-text: $side-nav-light-text, hover: $side-nav-light-hover, hover-text: $side-nav-light-text); } @@ -166,9 +166,7 @@ $side-nav-light-active: #484848; @function app-generate-status-theme($theme, $status-theme: null) { @if ($status-theme) { @return $status-theme; - } - - @else { + } @else { $warn: map-get($theme, warn); $primary: map-get($theme, primary); $white: #fff; // Use default palette for status diff --git a/src/frontend/packages/core/sass/components/mat-snack-bar.theme.scss b/src/frontend/packages/core/sass/components/mat-snack-bar.theme.scss new file mode 100644 index 0000000000..536d6434d7 --- /dev/null +++ b/src/frontend/packages/core/sass/components/mat-snack-bar.theme.scss @@ -0,0 +1,20 @@ +@mixin app-mat-snack-bar-theme($theme, $app-theme) { + $is-dark: map-get($theme, is-dark); + $background-colors: map-get($theme, background); + $foreground-colors: map-get($theme, foreground); + + $background-color: mat-color($foreground-colors, text); + $color: mat-color($background-colors, text); + + @if $is-dark == true { + $background-color: lighten(mat-color($background-colors, background), 5%); + $color: mat-color($foreground-colors, text); + } + + .mat-snack-bar-container { + background-color: $background-color; + .mat-simple-snackbar { + color: $color; + } + } +} diff --git a/src/frontend/packages/core/sass/components/mat-tabs.theme.scss b/src/frontend/packages/core/sass/components/mat-tabs.theme.scss deleted file mode 100644 index 29f7023533..0000000000 --- a/src/frontend/packages/core/sass/components/mat-tabs.theme.scss +++ /dev/null @@ -1,20 +0,0 @@ -// Fix the color of the underline for the active tab -// when on a primary backgrdound -// Need to revist this and check if this is an issue with the Angular Material library -@mixin app-mat-tabs-theme($theme, $app-theme) { - $is-dark: map-get($theme, is-dark); - $primary: map-get($theme, primary); - $tabs-ink-color: mat-color($primary); - @if $is-dark == true { - $tabs-ink-color: lighten($tabs-ink-color, 20%); - } @else { - $tabs-ink-color: darken($tabs-ink-color, 20%); - } - - .mat-tab-nav-bar.mat-primary.mat-background-primary { - .mat-ink-bar { - //background-color: $tabs-ink-color; - } - - } -} diff --git a/src/frontend/packages/core/sass/components/ngx-charts-gauge.theme.scss b/src/frontend/packages/core/sass/components/ngx-charts-gauge.theme.scss new file mode 100644 index 0000000000..406a49f950 --- /dev/null +++ b/src/frontend/packages/core/sass/components/ngx-charts-gauge.theme.scss @@ -0,0 +1,6 @@ +@mixin ngx-charts-gauge($theme, $app-theme) { + $foreground-colors: map-get($theme, foreground); + ngx-charts-chart text { + fill: mat-color($foreground-colors, text); + } +} diff --git a/src/frontend/packages/core/sass/theme.scss b/src/frontend/packages/core/sass/theme.scss index eb3e41787c..ed6428d931 100644 --- a/src/frontend/packages/core/sass/theme.scss +++ b/src/frontend/packages/core/sass/theme.scss @@ -6,18 +6,38 @@ // Custom theme support @import './custom'; -// Themes palettes and colors -$oss-theme-primary: mat-palette($mat-blue); -$oss-theme-accent: mat-palette($mat-blue); -$oss-theme-warn: mat-palette($mat-red); -$oss-theme: mat-light-theme($oss-theme-primary, $oss-theme-accent, $oss-theme-warn); +.dark-theme { + // Dark Theme defaults + $dark-primary: mat-palette($mat-blue); + $dark-accent: mat-palette($mat-amber, A400, A100, A700); + $dark-warn: mat-palette($mat-red); + $dark-theme: mat-dark-theme($dark-primary, $dark-accent, $dark-warn); -// Default to using the open source theme -$stratos-theme: $oss-theme !default; -$stratos-nav-theme: null !default; -$stratos-status-theme: null !default; + $stratos-dark-theme: $dark-theme !default; + $stratos-dark-nav-theme: null !default; + $stratos-dark-status-theme: null !default; + + @include angular-material-theme($stratos-dark-theme); + @include app-theme($stratos-dark-theme, $stratos-dark-nav-theme, $stratos-dark-status-theme); +} + +.default { + // Themes palettes and colors + $oss-theme-primary: mat-palette($mat-blue); + $oss-theme-accent: mat-palette($mat-blue); + $oss-theme-warn: mat-palette($mat-red); + $oss-theme: mat-light-theme($oss-theme-primary, $oss-theme-accent, $oss-theme-warn); + + // Default to using the open source theme + $stratos-theme: $oss-theme !default; + $stratos-nav-theme: null !default; + $stratos-status-theme: null !default; + + @include angular-material-theme($stratos-theme); + @include app-theme($stratos-theme, $stratos-nav-theme, $stratos-status-theme); +} + +$stratos-dark-theme-supported: true !default; // Create the theme -@include mat-core(); -@include angular-material-theme($stratos-theme); -@include app-theme($stratos-theme, $stratos-nav-theme, $stratos-status-theme); +@include mat-core; diff --git a/src/frontend/packages/core/src/app.component.html b/src/frontend/packages/core/src/app.component.html index 192fe40149..16017c9762 100644 --- a/src/frontend/packages/core/src/app.component.html +++ b/src/frontend/packages/core/src/app.component.html @@ -1,2 +1,5 @@ - -
{{userId}}
\ No newline at end of file + + + +
{{userId}}
+
\ No newline at end of file diff --git a/src/frontend/packages/core/src/app.component.ts b/src/frontend/packages/core/src/app.component.ts index 62df36b02f..5dce6095bc 100644 --- a/src/frontend/packages/core/src/app.component.ts +++ b/src/frontend/packages/core/src/app.component.ts @@ -4,6 +4,7 @@ import { Observable } from 'rxjs'; import { create } from 'rxjs-spy'; import { AuthOnlyAppState } from '../../store/src/app-state'; +import { ThemeService } from './core/theme.service'; import { environment } from './environments/environment'; import { LoggedInService } from './logged-in.service'; @@ -20,7 +21,8 @@ export class AppComponent implements OnInit, OnDestroy, AfterContentInit { constructor( private loggedInService: LoggedInService, - store: Store + store: Store, + public themeService: ThemeService ) { // We use the username to key the session storage. We could replace this with the users id? this.userId$ = store.select(state => state.auth.sessionData && state.auth.sessionData.user ? state.auth.sessionData.user.name : null); diff --git a/src/frontend/packages/core/src/core/style.service.ts b/src/frontend/packages/core/src/core/style.service.ts new file mode 100644 index 0000000000..f832f06c0d --- /dev/null +++ b/src/frontend/packages/core/src/core/style.service.ts @@ -0,0 +1,38 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root', +}) +export class StyleService { + + private rules: string[] = []; + constructor() { + this.rules = this.getAllSelectors(); + } + + hasSelector = (selector) => { + return !!this.rules.find(ruleSelector => ruleSelector === selector); + } + + private getAllSelectors = (): string[] => { + const ret = []; + // tslint:disable-next-line:prefer-for-of + for (let i = 0; i < document.styleSheets.length; i++) { + const styleSheet = document.styleSheets[i]; + if (!(styleSheet instanceof CSSStyleSheet)) { + continue; + } + const rules = styleSheet.rules || styleSheet.cssRules; + // tslint:disable-next-line:prefer-for-of + for (let y = 0; y < rules.length; y++) { + const rule = rules[y]; + if (!(rule instanceof CSSStyleRule)) { + continue; + } + if (typeof rule.selectorText === 'string') { ret.push(rule.selectorText); } + } + } + return ret; + } + +} diff --git a/src/frontend/packages/core/src/core/theme.service.ts b/src/frontend/packages/core/src/core/theme.service.ts new file mode 100644 index 0000000000..f5ee16ba18 --- /dev/null +++ b/src/frontend/packages/core/src/core/theme.service.ts @@ -0,0 +1,151 @@ +import { OverlayContainer } from '@angular/cdk/overlay'; +import { Injectable } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { Observable } from 'rxjs'; +import { first, map } from 'rxjs/operators'; + +import { SetThemeAction } from '../../../store/src/actions/dashboard-actions'; +import { DashboardOnlyAppState } from '../../../store/src/app-state'; +import { selectDashboardState } from '../../../store/src/selectors/dashboard.selectors'; +import { StyleService } from './style.service'; + +export interface StratosTheme { + key: string; + label: string; + styleName: string; +} + +const lightTheme: StratosTheme = { + key: 'default', + label: 'Light', + styleName: 'default' +}; +const darkTheme: StratosTheme = { + key: 'dark', + label: 'Dark', + styleName: 'dark-theme' +}; +const osTheme: StratosTheme = { + key: 'os', + label: 'OS', + styleName: '' +}; + +@Injectable({ + providedIn: 'root', +}) +export class ThemeService { + + private osThemeInfo = { + supports: false, + isDarkMode: window.matchMedia('(prefers-color-scheme: dark)').matches, + isLightMode: window.matchMedia('(prefers-color-scheme: light)').matches, + isNotSpecified: window.matchMedia('(prefers-color-scheme: no-preference)').matches + }; + private themes: StratosTheme[] = [lightTheme]; + + constructor( + private store: Store, + private overlayContainer: OverlayContainer, + private styleService: StyleService) { + this.initialiseStratosThemeInfo(); + } + + getThemes(): StratosTheme[] { + return this.themes; + } + + getTheme(): Observable { + return this.store.select(selectDashboardState).pipe( + map(dashboardState => this.findTheme(dashboardState.themeKey)), + ); + } + + setTheme(themeKey: string) { + const findTheme = this.findTheme(themeKey); + this.setOverlay(findTheme); + this.store.dispatch(new SetThemeAction(findTheme)); + } + + /** + * Initialize the service with a theme that may already exists in the store + */ + initialize() { + this.getTheme().pipe(first()).subscribe(theme => this.setOverlay(theme)); + } + + private initialiseStratosThemeInfo() { + const hasDarkTheme = this.styleService.hasSelector('.dark-theme-supported'); + + if (hasDarkTheme) { + this.themes.push(darkTheme); + + this.initialiseOsThemeInfo(); + } + + } + + private initialiseOsThemeInfo() { + this.osThemeInfo.supports = this.osThemeInfo.isDarkMode || this.osThemeInfo.isLightMode || this.osThemeInfo.isNotSpecified; + + if (this.osThemeInfo.supports) { + this.themes.push(osTheme); + + // Watch for changes at run time + window.matchMedia('(prefers-color-scheme: dark)').addListener(e => e.matches && this.updateFollowingOsThemeChange()); + window.matchMedia('(prefers-color-scheme: light)').addListener(e => e.matches && this.updateFollowingOsThemeChange()); + window.matchMedia('(prefers-color-scheme: no-preference)').addListener(e => e.matches && this.updateFollowingOsThemeChange()); + } + } + + /** + * Find a theme in a safe way with fall backs + */ + private findTheme(themeKey: string): StratosTheme { + if (themeKey === osTheme.key && this.getThemes().find(theme => theme.key === osTheme.key)) { + return this.getOsTheme() || lightTheme; + } + return this.getThemes().find(theme => theme.key === themeKey) || lightTheme; + } + + /** + * Create an `OS` theme that contains the relevant style + */ + private getOsTheme(): StratosTheme { + if (this.osThemeInfo.supports) { + return this.osThemeInfo.isDarkMode ? { + ...osTheme, + styleName: darkTheme.styleName + } : this.osThemeInfo.isLightMode || this.osThemeInfo.isNotSpecified ? { + ...osTheme, + styleName: lightTheme.styleName + } : null; + } + } + + /** + * Overlays require the theme specifically set, see https://material.angular.io/guide/theming#multiple-themes + * `Multiple themes and overlay-based components` + */ + private setOverlay(newTheme: StratosTheme) { + // Remove pre-existing styles + this.getThemes() + .filter(theme => theme.styleName) + .forEach(theme => this.overlayContainer.getContainerElement().classList.remove(theme.styleName)); + // Add new style (not from getThemes list, handles OS case) + this.overlayContainer.getContainerElement().classList.add(newTheme.styleName); + } + + /** + * Update theme given changes in OS theme settings + */ + private updateFollowingOsThemeChange() { + this.osThemeInfo.isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; + this.osThemeInfo.isLightMode = window.matchMedia('(prefers-color-scheme: light)').matches; + this.osThemeInfo.isNotSpecified = window.matchMedia('(prefers-color-scheme: no-preference)').matches; + + this.store.select(selectDashboardState).pipe( + first() + ).subscribe(dashboardState => dashboardState.themeKey === osTheme.key && this.setTheme(osTheme.key)); + } +} diff --git a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss index 0884263a2b..8c10e82191 100644 --- a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss +++ b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss @@ -3,7 +3,6 @@ $app-sub-header-height: 48px; .dashboard { - background-color: transparent; display: flex; flex-direction: column; height: 100%; diff --git a/src/frontend/packages/core/src/features/dashboard/page-side-nav/page-side-nav.component.theme.scss b/src/frontend/packages/core/src/features/dashboard/page-side-nav/page-side-nav.component.theme.scss index a05435ff5a..d7c1ebb669 100644 --- a/src/frontend/packages/core/src/features/dashboard/page-side-nav/page-side-nav.component.theme.scss +++ b/src/frontend/packages/core/src/features/dashboard/page-side-nav/page-side-nav.component.theme.scss @@ -23,7 +23,7 @@ } } &__item { - color: rgba(0, 0, 0, .6); + color: map-get($app-theme, subdued-color); &--active { background-color: transparentize($primary-color, .9); color: $primary-color; diff --git a/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.html b/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.html index 93feabd5f5..7e807d5687 100644 --- a/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.html +++ b/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.html @@ -92,6 +92,18 @@

User Profile

polling may result in some pages showing out-of-date information. + diff --git a/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.ts b/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.ts index fda5d8da05..b504c6832f 100644 --- a/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.ts +++ b/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.ts @@ -7,10 +7,11 @@ import { SetPollingEnabledAction, SetSessionTimeoutAction } from '../../../../.. import { DashboardOnlyAppState } from '../../../../../store/src/app-state'; import { selectDashboardState } from '../../../../../store/src/selectors/dashboard.selectors'; import { UserProfileInfo } from '../../../../../store/src/types/user-profile.types'; +import { ThemeService } from '../../../core/theme.service'; +import { UserService } from '../../../core/user.service'; import { ConfirmationDialogConfig } from '../../../shared/components/confirmation-dialog.config'; import { ConfirmationDialogService } from '../../../shared/components/confirmation-dialog.service'; import { UserProfileService } from '../user-profile.service'; -import { UserService } from '../../../core/user.service'; @Component({ selector: 'app-profile-info', @@ -31,6 +32,7 @@ export class ProfileInfoComponent implements OnInit { userProfile$: Observable; primaryEmailAddress$: Observable; + hasMultipleThemes: boolean; private sessionDialogConfig = new ConfirmationDialogConfig( 'Disable session timeout', @@ -60,6 +62,7 @@ export class ProfileInfoComponent implements OnInit { private store: Store, private confirmDialog: ConfirmationDialogService, public userService: UserService, + public themeService: ThemeService ) { this.isError$ = userProfileService.isError$; this.userProfile$ = userProfileService.userProfile$; @@ -67,6 +70,8 @@ export class ProfileInfoComponent implements OnInit { this.primaryEmailAddress$ = this.userProfile$.pipe( map((profile: UserProfileInfo) => userProfileService.getPrimaryEmailAddress(profile)) ); + + this.hasMultipleThemes = themeService.getThemes().length > 1; } ngOnInit() { diff --git a/src/frontend/packages/core/src/shared/components/code-block/code-block.component.theme.scss b/src/frontend/packages/core/src/shared/components/code-block/code-block.component.theme.scss index 1010773073..bfac5a8b6e 100644 --- a/src/frontend/packages/core/src/shared/components/code-block/code-block.component.theme.scss +++ b/src/frontend/packages/core/src/shared/components/code-block/code-block.component.theme.scss @@ -1,16 +1,22 @@ @import '~@angular/material/theming'; @mixin display-value-theme($theme, $app-theme) { + $is-dark: map-get($theme, is-dark); $primary: map-get($theme, primary); $background-colors: map-get($theme, background); $foreground-colors: map-get($theme, foreground); .app-code-block { - background-color: mat-color($foreground-colors, text); - color: darken(mat-color($background-colors, background), 2%); - &___copied-icon { - color: mat-color($primary); + $background-color: mat-color($foreground-colors, text); + $color: darken(mat-color($background-colors, background), 2%); + @if $is-dark == true { + // See the app variable and cf cli pages + $background-color: lighten(mat-color($background-colors, background), 5%); + $color: mat-color($foreground-colors, text); } - &__copied-div { - background-color: mat-color($foreground-colors, text); + + background-color: $background-color; + color: $color; + &__copied-icon { + color: mat-color($primary); } } } diff --git a/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.scss b/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.scss index ab1ec9abec..7ad3af708e 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.scss +++ b/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.scss @@ -1,6 +1,5 @@ .meta-card-item-row, .meta-card-item-row-top { - $text-color: rgba(0, 0, 0, .6); align-items: center; display: flex; justify-content: space-between; @@ -20,8 +19,6 @@ } .meta-card-item__key { - $text-color: rgba(0, 0, 0, .6); - color: $text-color; flex: none; font-weight: 300; padding-right: 10px; @@ -51,7 +48,6 @@ line-height: 18px; position: relative; &::after { - background: linear-gradient(to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1) 75%); bottom: 0; content: ''; height: 1.2em; diff --git a/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.theme.scss b/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.theme.scss new file mode 100644 index 0000000000..890951c936 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.theme.scss @@ -0,0 +1,12 @@ +@mixin app-meta-card-item-theme($theme, $app-theme) { + $backgrounds: map-get($theme, background); + $background: mat-color($backgrounds, card); + + .meta-card-item-long-text-fixed { + .meta-card-item__value { + &::after { + background: linear-gradient(to right, rgba(255, 255, 255, 0), $background 50%); + } + } + } +} diff --git a/src/frontend/packages/core/src/shared/components/list/list.component.theme.scss b/src/frontend/packages/core/src/shared/components/list/list.component.theme.scss index 6c51012b83..c7e86ff821 100644 --- a/src/frontend/packages/core/src/shared/components/list/list.component.theme.scss +++ b/src/frontend/packages/core/src/shared/components/list/list.component.theme.scss @@ -4,10 +4,19 @@ $primary: map-get($theme, primary); $foreground-colors: map-get($theme, foreground); $background-colors: map-get($theme, background); + $is-dark: map-get($theme, is-dark); + + $header-selected-color: #fff; + @if $is-dark == true { + $header-selected-color: lighten(mat-color($background-colors, background), 20%); + } @else { + $header-selected-color: mat-color($primary, 50); + } + .list-component { &__header { &--selected { - background-color: mat-color($primary, 50); + background-color: $header-selected-color; } &__right, &__left--multi-filters { diff --git a/src/frontend/packages/core/src/shared/components/page-header/page-header.component.theme.scss b/src/frontend/packages/core/src/shared/components/page-header/page-header.component.theme.scss index 736c50de6b..65ed55245f 100644 --- a/src/frontend/packages/core/src/shared/components/page-header/page-header.component.theme.scss +++ b/src/frontend/packages/core/src/shared/components/page-header/page-header.component.theme.scss @@ -7,6 +7,7 @@ $error: map-get($status-colors, danger); $warning: map-get($status-colors, warning); $info: lighten($primmary-color, 20%); + $subdued: map-get($app-theme, subdued-color); .page-header { &__warning-count { background-color: mat-color($primary); @@ -18,6 +19,12 @@ &__menu-separator { background-color: mat-color($foreground, divider); } + &__menu-inner { + color: mat-contrast($primary, 500); + } + &__history { + color: $subdued; + } &__underflow { background-color: mat-color($primary); } diff --git a/src/frontend/packages/core/src/shared/shared.module.ts b/src/frontend/packages/core/src/shared/shared.module.ts index 12aae65f83..4447caf735 100644 --- a/src/frontend/packages/core/src/shared/shared.module.ts +++ b/src/frontend/packages/core/src/shared/shared.module.ts @@ -58,8 +58,12 @@ import { TableCellStatusDirective } from './components/list/list-table/table-cel import { TableComponent } from './components/list/list-table/table.component'; import { listTableComponents } from './components/list/list-table/table.types'; import { EndpointCardComponent } from './components/list/list-types/endpoint/endpoint-card/endpoint-card.component'; +import { EndpointListHelper } from './components/list/list-types/endpoint/endpoint-list.helpers'; +import { EndpointsListConfigService } from './components/list/list-types/endpoint/endpoints-list-config.service'; import { ListComponent } from './components/list/list.component'; import { ListConfig } from './components/list/list.component.types'; +import { ListHostDirective } from './components/list/simple-list/list-host.directive'; +import { SimpleListComponent } from './components/list/simple-list/simple-list.component'; import { LoadingPageComponent } from './components/loading-page/loading-page.component'; import { LogViewerComponent } from './components/log-viewer/log-viewer.component'; import { MarkdownContentObserverDirective } from './components/markdown-preview/markdown-content-observer.directive'; @@ -112,10 +116,6 @@ import { ValuesPipe } from './pipes/values.pipe'; import { CloudFoundryUserProvidedServicesService } from './services/cloud-foundry-user-provided-services.service'; import { MetricsRangeSelectorService } from './services/metrics-range-selector.service'; import { UserPermissionDirective } from './user-permission.directive'; -import { SimpleListComponent } from './components/list/simple-list/simple-list.component'; -import { ListHostDirective } from './components/list/simple-list/list-host.directive'; -import { EndpointListHelper } from './components/list/list-types/endpoint/endpoint-list.helpers'; -import { EndpointsListConfigService } from './components/list/list-types/endpoint/endpoints-list-config.service'; /* tslint:disable:max-line-length */ diff --git a/src/frontend/packages/core/src/styles.scss b/src/frontend/packages/core/src/styles.scss index 1fe35bd298..f6e062111e 100644 --- a/src/frontend/packages/core/src/styles.scss +++ b/src/frontend/packages/core/src/styles.scss @@ -65,3 +65,11 @@ button.mat-simple-snackbar-action { // flex: 1; // flex-direction: column; // } + + +// Add selector so that the UI can detect if a dark theme is available +@if $stratos-dark-theme-supported { + .dark-theme-supported { + margin: 0; + } +} diff --git a/src/frontend/packages/core/test-framework/store-test-helper.ts b/src/frontend/packages/core/test-framework/store-test-helper.ts index c2383e2937..3a6277b913 100644 --- a/src/frontend/packages/core/test-framework/store-test-helper.ts +++ b/src/frontend/packages/core/test-framework/store-test-helper.ts @@ -164,7 +164,8 @@ function getDefaultInitialTestStratosStoreState() { isMobile: false, isMobileNavOpen: false, sideNavPinned: false, - pollingEnabled: true + pollingEnabled: true, + themeKey: null }, actionHistory: [], lists: {}, diff --git a/src/frontend/packages/store/src/actions/dashboard-actions.ts b/src/frontend/packages/store/src/actions/dashboard-actions.ts index 88c8f8019c..3918585f85 100644 --- a/src/frontend/packages/store/src/actions/dashboard-actions.ts +++ b/src/frontend/packages/store/src/actions/dashboard-actions.ts @@ -1,5 +1,6 @@ import { Action } from '@ngrx/store'; +import { StratosTheme } from '../../../core/src/core/theme.service'; import { DashboardState } from '../reducers/dashboard-reducer'; export const OPEN_SIDE_NAV = '[Dashboard] Open side nav'; @@ -16,6 +17,7 @@ export const CLOSE_SIDE_HELP = '[Dashboard] Close side help'; export const TIMEOUT_SESSION = '[Dashboard] Timeout Session'; export const ENABLE_POLLING = '[Dashboard] Enable Polling'; +export const SET_STRATOS_THEME = '[Dashboard] Set Theme'; export const HYDRATE_DASHBOARD_STATE = '[Dashboard] Hydrate dashboard state'; @@ -73,3 +75,7 @@ export class HydrateDashboardStateAction implements Action { type = HYDRATE_DASHBOARD_STATE; } +export class SetThemeAction implements Action { + constructor(public theme: StratosTheme) { } + type = SET_STRATOS_THEME; +} diff --git a/src/frontend/packages/store/src/effects/dashboard.effects.ts b/src/frontend/packages/store/src/effects/dashboard.effects.ts new file mode 100644 index 0000000000..0e796db044 --- /dev/null +++ b/src/frontend/packages/store/src/effects/dashboard.effects.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@angular/core'; +import { Actions, Effect, ofType } from '@ngrx/effects'; +import { map } from 'rxjs/operators'; + +import { ThemeService } from '../../../core/src/core/theme.service'; +import { HYDRATE_DASHBOARD_STATE, HydrateDashboardStateAction } from '../actions/dashboard-actions'; + + +@Injectable() +export class DashboardEffect { + + constructor( + private actions$: Actions, + private themeService: ThemeService + ) { } + + @Effect({ dispatch: false }) hydrate$ = this.actions$.pipe( + ofType(HYDRATE_DASHBOARD_STATE), + map(() => { + // Ensure the previous theme is applied + this.themeService.initialize(); + }) + ); +} diff --git a/src/frontend/packages/store/src/reducers/dashboard-reducer.ts b/src/frontend/packages/store/src/reducers/dashboard-reducer.ts index 5963b912b9..b5f606d521 100644 --- a/src/frontend/packages/store/src/reducers/dashboard-reducer.ts +++ b/src/frontend/packages/store/src/reducers/dashboard-reducer.ts @@ -7,8 +7,10 @@ import { HYDRATE_DASHBOARD_STATE, HydrateDashboardStateAction, OPEN_SIDE_NAV, + SET_STRATOS_THEME, SetPollingEnabledAction, SetSessionTimeoutAction, + SetThemeAction, SHOW_SIDE_HELP, TIMEOUT_SESSION, TOGGLE_SIDE_NAV, @@ -23,6 +25,7 @@ export interface DashboardState { sideNavPinned: boolean; sideHelpOpen: boolean; sideHelpDocument: string; + themeKey: string; } export const defaultDashboardState: DashboardState = { @@ -34,6 +37,7 @@ export const defaultDashboardState: DashboardState = { sideNavPinned: true, sideHelpOpen: false, sideHelpDocument: null, + themeKey: null }; export function dashboardReducer(state: DashboardState = defaultDashboardState, action): DashboardState { @@ -79,6 +83,12 @@ export function dashboardReducer(state: DashboardState = defaultDashboardState, ...state, ...hydrateDashboardStateAction.dashboardState }; + case SET_STRATOS_THEME: + const setThemeAction = action as SetThemeAction; + return { + ...state, + themeKey: setThemeAction.theme ? setThemeAction.theme.key : null + }; default: return state; } diff --git a/src/frontend/packages/store/src/store.module.ts b/src/frontend/packages/store/src/store.module.ts index 386db19890..14c8160af6 100644 --- a/src/frontend/packages/store/src/store.module.ts +++ b/src/frontend/packages/store/src/store.module.ts @@ -6,6 +6,7 @@ import { ActionHistoryEffect } from './effects/action-history.effects'; import { APIEffect } from './effects/api.effects'; import { AppEffects } from './effects/app.effects'; import { AuthEffect } from './effects/auth.effects'; +import { DashboardEffect } from './effects/dashboard.effects'; import { EndpointApiError } from './effects/endpoint-api-errors.effects'; import { EndpointsEffect } from './effects/endpoint.effects'; import { MetricsEffect } from './effects/metrics.effects'; @@ -21,8 +22,8 @@ import { UpdateAppEffects } from './effects/update-app-effects'; import { UserFavoritesEffect } from './effects/user-favorites-effect'; import { UserProfileEffect } from './effects/user-profile.effects'; import { UsersRolesEffects } from './effects/users-roles.effects'; -import { AppReducersModule } from './reducers.module'; import { PipelineHttpClient } from './entity-request-pipeline/pipline-http-client.service'; +import { AppReducersModule } from './reducers.module'; @NgModule({ @@ -33,6 +34,7 @@ import { PipelineHttpClient } from './entity-request-pipeline/pipline-http-clien AppReducersModule, HttpClientModule, EffectsModule.forRoot([ + DashboardEffect, APIEffect, EndpointApiError, AuthEffect, From 7fbed760d8441b68b33f581d22d5c1bba5df4f70 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 5 Dec 2019 13:22:07 +0000 Subject: [PATCH 26/29] Bump github.com/mattn/go-sqlite3 from 1.10.0 to 1.13.0 in /src/jetstream (#4032) Bumps [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) from 1.10.0 to 1.13.0. - [Release notes](https://github.com/mattn/go-sqlite3/releases) - [Commits](https://github.com/mattn/go-sqlite3/compare/v1.10.0...v1.13.0) Signed-off-by: dependabot-preview[bot] --- src/jetstream/go.mod | 2 +- src/jetstream/go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/jetstream/go.mod b/src/jetstream/go.mod index 00ad025086..12bb0de986 100644 --- a/src/jetstream/go.mod +++ b/src/jetstream/go.mod @@ -50,7 +50,7 @@ require ( github.com/mattn/go-colorable v0.1.4 // indirect github.com/mattn/go-isatty v0.0.10 // indirect github.com/mattn/go-runewidth v0.0.4 // indirect - github.com/mattn/go-sqlite3 v1.10.0 + github.com/mattn/go-sqlite3 v1.13.0 github.com/mholt/archiver v3.1.1+incompatible github.com/mitchellh/mapstructure v1.1.2 // indirect github.com/moby/moby v1.13.1 // indirect diff --git a/src/jetstream/go.sum b/src/jetstream/go.sum index f4e13fbd76..351a27e60d 100644 --- a/src/jetstream/go.sum +++ b/src/jetstream/go.sum @@ -203,6 +203,8 @@ github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/ github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.13.0 h1:LnJI81JidiW9r7pS/hXe6cFeO5EXNq7KbfvoJLRI69c= +github.com/mattn/go-sqlite3 v1.13.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU= From 7634b227a464a5380cc0d969f3904442429f5015 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 5 Dec 2019 13:29:28 +0000 Subject: [PATCH 27/29] Bump to v3 (#4042) --- manifest.yml | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/manifest.yml b/manifest.yml index fb4a04acad..9d22373b44 100644 --- a/manifest.yml +++ b/manifest.yml @@ -4,7 +4,7 @@ applications: disk_quota: 1024M host: console timeout: 180 - buildpack: https://github.com/cloudfoundry-incubator/stratos-buildpack#v2.4 + buildpack: https://github.com/cloudfoundry-incubator/stratos-buildpack#v3 health-check-type: port # env: # Override CF API endpoint URL inferred from VCAP_APPLICATION env diff --git a/package-lock.json b/package-lock.json index 46c8e32a58..a548d0cc34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "stratos", - "version": "2.6.1", + "version": "3.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index acaac983a2..7856c8e7ed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stratos", - "version": "2.6.1", + "version": "3.0.0", "description": "Stratos Console", "main": "index.js", "scripts": { From dcca1627d1c76260c6eea33f37bd62dc82d72cbd Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Thu, 5 Dec 2019 13:32:37 +0000 Subject: [PATCH 28/29] E2E Improvements (#4035) * Bump * Revert "Bump" This reverts commit f26d9db0940777ae89dfcb6bdc73bc91658a4b6a. * Revert "Revert "Bump"" This reverts commit 245facc1ca19c8f322dfea34d1c33dd2e605c344. * Few minor tweaks --- README.md | 1 + .../application-delete-e2e.spec.ts | 6 +- .../application-deploy-e2e.spec.ts | 169 +++++++++--------- .../po/application-page-variables.po.ts | 2 +- .../application/po/application-page.po.ts | 2 +- .../space-level/cf-space-level-e2e.spec.ts | 72 ++++---- src/test-e2e/po/cf-users-list.po.ts | 4 +- src/test-e2e/po/confirm-dialog.ts | 16 +- 8 files changed, 148 insertions(+), 124 deletions(-) diff --git a/README.md b/README.md index ad7817d1d1..d98bde0082 100644 --- a/README.md +++ b/README.md @@ -75,3 +75,4 @@ Tested with Browserstack ## License The work done has been licensed under Apache License 2.0. The license file can be found [here](LICENSE). + diff --git a/src/test-e2e/application/application-delete-e2e.spec.ts b/src/test-e2e/application/application-delete-e2e.spec.ts index e866920677..c6e6b74ffb 100644 --- a/src/test-e2e/application/application-delete-e2e.spec.ts +++ b/src/test-e2e/application/application-delete-e2e.spec.ts @@ -58,7 +58,7 @@ describe('Application Delete', () => { appSummaryPage.navigateTo(); appSummaryPage.waitForPage(40000); // Open delete app dialog - const deleteApp = appSummaryPage.delete(testAppName); + const deleteApp = appSummaryPage.delete(); // App did not have a route, so there should be no routes step expect(deleteApp.hasRouteStep()).toBeFalsy(); // 1 step - np header shown @@ -71,7 +71,7 @@ describe('Application Delete', () => { }); describe('Long running tests', () => { - const timeout = 100000; + const timeout = 120000; extendE2ETestTime(timeout); beforeAll(() => { @@ -93,7 +93,7 @@ describe('Application Delete', () => { const appSummaryPage = new ApplicationBasePage(cfGuid, app.metadata.guid); appSummaryPage.navigateTo(); appSummaryPage.waitForPage(); - const deleteApp = appSummaryPage.delete(testAppName); + const deleteApp = appSummaryPage.delete(); // App did not have a route, so there should be no routes step expect(deleteApp.hasRouteStep()).toBeFalsy(); diff --git a/src/test-e2e/application/application-deploy-e2e.spec.ts b/src/test-e2e/application/application-deploy-e2e.spec.ts index 1c8e349a74..519bfe11c2 100644 --- a/src/test-e2e/application/application-deploy-e2e.spec.ts +++ b/src/test-e2e/application/application-deploy-e2e.spec.ts @@ -204,7 +204,8 @@ describe('Application Deploy -', () => { }); }); - describe('Tab Tests -', () => { + describe('Tab Tests (app status independent) -', () => { + // These tests don't rely on the app status beforeAll(() => { // Should be deployed, no web-socket open, so we can wait for angular again @@ -267,105 +268,109 @@ describe('Application Deploy -', () => { }); }); - it('App Summary', () => { - // Does app to be fully started - const appSummary = new ApplicationPageSummaryTab(appDetails.cfGuid, appDetails.appGuid); - appSummary.goToSummaryTab(); + describe('Tab Tests (app status dependent) -', () => { - appSummary.cardStatus.getStatus().then(res => { - expect(res.status).toBe('Deployed'); - expect(res.subStatus).toBe('Online'); - }); - - appSummary.cardInstances.waitForRunningInstancesText('1 / 1'); + it('App Summary', () => { + // Does app to be fully started + const appSummary = new ApplicationPageSummaryTab(appDetails.cfGuid, appDetails.appGuid); + appSummary.goToSummaryTab(); - expect(appSummary.cardUptime.getTitle()).not.toBe('Application is not running'); - expect(appSummary.cardUptime.getUptime().isDisplayed()).toBeTruthy(); - expect(appSummary.cardUptime.getUptimeText()).not.toBeNull(); + appSummary.cardStatus.getStatus().then(res => { + expect(res.status).toBe('Deployed'); + expect(res.subStatus).toBe('Online'); + }); - expect(appSummary.cardInfo.memQuota.getValue()).toBe('16 MB'); - expect(appSummary.cardInfo.diskQuota.getValue()).toBe('64 MB'); - expect(appSummary.cardInfo.appState.getValue()).toBe('STARTED'); - expect(appSummary.cardInfo.packageState.getValue()).toBe('STAGED'); - expect(appSummary.cardInfo.services.getValue()).toBe('0'); - expect(appSummary.cardInfo.routes.getValue()).toBe('1'); + appSummary.cardInstances.waitForRunningInstancesText('1 / 1'); - expect(appSummary.cardCfInfo.cf.getValue()).toBe(cfName); - expect(appSummary.cardCfInfo.org.getValue()).toBe(orgName); - expect(appSummary.cardCfInfo.space.getValue()).toBe(spaceName); + expect(appSummary.cardUptime.getTitle()).not.toBe('Application is not running'); + expect(appSummary.cardUptime.getUptime().isDisplayed()).toBeTruthy(); + expect(appSummary.cardUptime.getUptimeText()).not.toBeNull(); - expect(appSummary.cardBuildInfo.buildPack.getValue()).toBe('binary_buildpack'); - expect(appSummary.cardBuildInfo.stack.getValue()).toBe(testAppStack || defaultStack); + expect(appSummary.cardInfo.memQuota.getValue()).toBe('16 MB'); + expect(appSummary.cardInfo.diskQuota.getValue()).toBe('64 MB'); + expect(appSummary.cardInfo.appState.getValue()).toBe('STARTED'); + expect(appSummary.cardInfo.packageState.getValue()).toBe('STAGED'); + expect(appSummary.cardInfo.services.getValue()).toBe('0'); + expect(appSummary.cardInfo.routes.getValue()).toBe('1'); - appSummary.cardDeployInfo.waitForTitle('Deployment Info'); - appSummary.cardDeployInfo.github.waitUntilShown('Waiting for GitHub deployment information'); - expect(appSummary.cardDeployInfo.github.isDisplayed()).toBeTruthy(); - appSummary.cardDeployInfo.github.getValue().then(commitHash => { - expect(commitHash).toBeDefined(); - expect(commitHash.length).toBe(8); - }); + expect(appSummary.cardCfInfo.cf.getValue()).toBe(cfName); + expect(appSummary.cardCfInfo.org.getValue()).toBe(orgName); + expect(appSummary.cardCfInfo.space.getValue()).toBe(spaceName); - }); + expect(appSummary.cardBuildInfo.buildPack.getValue()).toBe('binary_buildpack'); + expect(appSummary.cardBuildInfo.stack.getValue()).toBe(testAppStack || defaultStack); - it('Instances Tab', () => { - // Does app to be fully started - const appInstances = new ApplicationPageInstancesTab(appDetails.cfGuid, appDetails.appGuid); - appInstances.goToInstancesTab(); + appSummary.cardDeployInfo.waitForTitle('Deployment Info'); + appSummary.cardDeployInfo.github.waitUntilShown('Waiting for GitHub deployment information'); + expect(appSummary.cardDeployInfo.github.isDisplayed()).toBeTruthy(); + appSummary.cardDeployInfo.github.getValue().then(commitHash => { + expect(commitHash).toBeDefined(); + expect(commitHash.length).toBe(8); + }); - appInstances.cardStatus.getStatus().then(res => { - expect(res.status).toBe('Deployed'); - expect(res.subStatus).toBe('Online'); }); - appInstances.cardInstances.waitForRunningInstancesText('1 / 1'); + it('Instances Tab', () => { + // Does app to be fully started + const appInstances = new ApplicationPageInstancesTab(appDetails.cfGuid, appDetails.appGuid); + appInstances.goToInstancesTab(); - expect(appInstances.cardUsage.getTitleElement().isPresent()).toBeFalsy(); - expect(appInstances.cardUsage.getUsageTable().isDisplayed()).toBeTruthy(); + appInstances.cardStatus.getStatus().then(res => { + expect(res.status).toBe('Deployed'); + expect(res.subStatus).toBe('Online'); + }); - expect(appInstances.list.empty.getDefault().isPresent()).toBeFalsy(); - expect(appInstances.list.table.getCell(0, 1).getText()).toBe('RUNNING'); + appInstances.cardInstances.waitForRunningInstancesText('1 / 1'); - }); + expect(appInstances.cardUsage.getTitleElement().isPresent()).toBeFalsy(); + expect(appInstances.cardUsage.getUsageTable().isDisplayed()).toBeTruthy(); - it('Routes Tab', () => { - const appRoutes = new ApplicationPageRoutesTab(appDetails.cfGuid, appDetails.appGuid); - appRoutes.goToRoutesTab(); - - expect(appRoutes.list.empty.getDefault().isPresent()).toBeFalsy(); - expect(appRoutes.list.empty.getCustom().getComponent().isPresent()).toBeFalsy(); - appRoutes.list.table.getCell(0, 1).getText().then((route: string) => { - expect(route).not.toBeNull(); - expect(route.length).toBeGreaterThan(testAppName.length); - const randomRouteStyleAppName = testAppName.replace(/[\.:]/g, ''); - expect(route.startsWith(randomRouteStyleAppName.substring(0, randomRouteStyleAppName.length - 11), 7)).toBeTruthy(); + expect(appInstances.list.empty.getDefault().isPresent()).toBeFalsy(); + expect(appInstances.list.table.getCell(0, 1).getText()).toBe('RUNNING'); + + }); + + it('Routes Tab', () => { + const appRoutes = new ApplicationPageRoutesTab(appDetails.cfGuid, appDetails.appGuid); + appRoutes.goToRoutesTab(); + + expect(appRoutes.list.empty.getDefault().isPresent()).toBeFalsy(); + expect(appRoutes.list.empty.getCustom().getComponent().isPresent()).toBeFalsy(); + appRoutes.list.table.getCell(0, 1).getText().then((route: string) => { + expect(route).not.toBeNull(); + expect(route.length).toBeGreaterThan(testAppName.length); + const randomRouteStyleAppName = testAppName.replace(/[\.:]/g, ''); + expect(route.startsWith(randomRouteStyleAppName.substring(0, randomRouteStyleAppName.length - 11), 7)).toBeTruthy(); + }); + appRoutes.list.table.getCell(0, 2).getText().then((tcpRoute: string) => { + expect(tcpRoute).not.toBeNull(); + expect(tcpRoute).toBe('highlight_off'); + }); }); - appRoutes.list.table.getCell(0, 2).getText().then((tcpRoute: string) => { - expect(tcpRoute).not.toBeNull(); - expect(tcpRoute).toBe('highlight_off'); + + it('Events Tab', () => { + // Does app to be fully started + const appEvents = new ApplicationPageEventsTab(appDetails.cfGuid, appDetails.appGuid); + appEvents.goToEventsTab(); + + expect(appEvents.list.empty.isDisplayed()).toBeFalsy(); + expect(appEvents.list.isTableView()).toBeTruthy(); + expect(appEvents.list.getTotalResults()).toBeGreaterThanOrEqual(3); + // Ensure that the earliest events are at the top + appEvents.list.table.toggleSort('Timestamp'); + + const currentUser = e2e.secrets.getDefaultCFEndpoint().creds.nonAdmin.username; + // Create + expect(appEvents.list.table.getCell(0, 1).getText()).toBe('audit\napp\ncreate'); + expect(appEvents.list.table.getCell(0, 2).getText()).toBe(`person\n${currentUser}`); + // Map Route + expect(appEvents.list.table.getCell(1, 1).getText()).toBe('audit\napp\nmap-route'); + expect(appEvents.list.table.getCell(1, 2).getText()).toBe(`person\n${currentUser}`); + // Update (route) + expect(appEvents.list.table.getCell(2, 1).getText()).toBe('audit\napp\nupdate'); + expect(appEvents.list.table.getCell(2, 2).getText()).toBe(`person\n${currentUser}`); }); - }); - it('Events Tab', () => { - // Does app to be fully started - const appEvents = new ApplicationPageEventsTab(appDetails.cfGuid, appDetails.appGuid); - appEvents.goToEventsTab(); - - expect(appEvents.list.empty.isDisplayed()).toBeFalsy(); - expect(appEvents.list.isTableView()).toBeTruthy(); - expect(appEvents.list.getTotalResults()).toBeGreaterThanOrEqual(2); - // Ensure that the earliest events are at the top - appEvents.list.table.toggleSort('Timestamp'); - - const currentUser = e2e.secrets.getDefaultCFEndpoint().creds.nonAdmin.username; - // Create - expect(appEvents.list.table.getCell(0, 1).getText()).toBe('audit\napp\ncreate'); - expect(appEvents.list.table.getCell(0, 2).getText()).toBe(`person\n${currentUser}`); - // Map Route - expect(appEvents.list.table.getCell(1, 1).getText()).toBe('audit\napp\nmap-route'); - expect(appEvents.list.table.getCell(1, 2).getText()).toBe(`person\n${currentUser}`); - // Update (route) - expect(appEvents.list.table.getCell(2, 1).getText()).toBe('audit\napp\nupdate'); - expect(appEvents.list.table.getCell(2, 2).getText()).toBe(`person\n${currentUser}`); }); describe('Instance scaling', () => { diff --git a/src/test-e2e/application/po/application-page-variables.po.ts b/src/test-e2e/application/po/application-page-variables.po.ts index b754ed6a30..679901c539 100644 --- a/src/test-e2e/application/po/application-page-variables.po.ts +++ b/src/test-e2e/application/po/application-page-variables.po.ts @@ -32,7 +32,7 @@ export class ApplicationPageVariablesTab extends ApplicationBasePage { this.list.table.openRowActionMenuByIndex(rowIndex).clickItem('Delete'); const confirm = new ConfirmDialogComponent(); confirm.waitUntilShown(); - expect(confirm.getMessage()).toBe(`Are you sure you want to delete '${variableName}'?`); + confirm.waitForMessage(`Are you sure you want to delete '${variableName}'?`); confirm.confirm(); return this.list.table.waitUntilNotBusy(); } diff --git a/src/test-e2e/application/po/application-page.po.ts b/src/test-e2e/application/po/application-page.po.ts index cb32b19887..07b2a5ad17 100644 --- a/src/test-e2e/application/po/application-page.po.ts +++ b/src/test-e2e/application/po/application-page.po.ts @@ -68,7 +68,7 @@ export class ApplicationBasePage extends CFPage { return this.header.getTitleText(); } - public delete(appName: string): DeleteApplication { + public delete(): DeleteApplication { const deleteApp = new DeleteApplication(this.cfGuid, this.appGuid); this.subHeader.clickIconButton('delete'); deleteApp.waitForPage(); diff --git a/src/test-e2e/cloud-foundry/space-level/cf-space-level-e2e.spec.ts b/src/test-e2e/cloud-foundry/space-level/cf-space-level-e2e.spec.ts index 36ef6c6792..f7b6095de0 100644 --- a/src/test-e2e/cloud-foundry/space-level/cf-space-level-e2e.spec.ts +++ b/src/test-e2e/cloud-foundry/space-level/cf-space-level-e2e.spec.ts @@ -4,6 +4,7 @@ import { e2e } from '../../e2e'; import { E2EConfigCloudFoundry } from '../../e2e.types'; import { CFHelpers } from '../../helpers/cf-helpers'; import { ConsoleUserType, E2EHelpers } from '../../helpers/e2e-helpers'; +import { extendE2ETestTime } from '../../helpers/extend-test-helpers'; import { CFPage } from '../../po/cf-page.po'; import { ListComponent } from '../../po/list.po'; import { MetaCardTitleType } from '../../po/meta-card.po'; @@ -42,36 +43,45 @@ describe('CF - Space Level -', () => { } function navToPage() { - const page = new CFPage(); - page.sideNav.goto(SideNavMenuItem.CloudFoundry); - CfTopLevelPage.detect().then(cfPage => { - cfPage.waitForPageOrChildPage(); - cfPage.loadingIndicator.waitUntilNotShown(); - cfPage.goToOrgTab(); - - // Find the Org and click on it - const list = new ListComponent(); - list.cards.findCardByTitle(defaultCf.testOrg, MetaCardTitleType.CUSTOM, true).then(card => { - expect(card).toBeDefined(); - card.click(); - }); - CfOrgLevelPage.detect().then(orgPage => { - orgPage.waitForPageOrChildPage(); - orgPage.loadingIndicator.waitUntilNotShown(); - orgPage.goToSpacesTab(); - - // Find the Space and click on it - const spaceList = new ListComponent(); - spaceList.cards.findCardByTitle(defaultCf.testSpace, MetaCardTitleType.CUSTOM, true).then(card => { - expect(card).toBeDefined(); - card.click(); - }); - CfSpaceLevelPage.detect().then(s => { - spacePage = s; - spacePage.waitForPageOrChildPage(); - spacePage.loadingIndicator.waitUntilNotShown(); + describe('', () => { + + // Allow additional time for navigation + extendE2ETestTime(70000); + + // Tests that the given users can navigate through the org and space lists + it('Nav to Space', () => { + const page = new CFPage(); + page.sideNav.goto(SideNavMenuItem.CloudFoundry); + CfTopLevelPage.detect().then(cfPage => { + cfPage.waitForPageOrChildPage(); + cfPage.loadingIndicator.waitUntilNotShown(); + cfPage.goToOrgTab(); + + // Find the Org and click on it + const list = new ListComponent(); + list.cards.findCardByTitle(defaultCf.testOrg, MetaCardTitleType.CUSTOM, true).then(card => { + expect(card).toBeDefined(); + card.click(); + }); + CfOrgLevelPage.detect().then(orgPage => { + orgPage.waitForPageOrChildPage(); + orgPage.loadingIndicator.waitUntilNotShown(); + orgPage.goToSpacesTab(); + + // Find the Space and click on it + const spaceList = new ListComponent(); + spaceList.cards.findCardByTitle(defaultCf.testSpace, MetaCardTitleType.CUSTOM, true).then(card => { + expect(card).toBeDefined(); + card.click(); + }); + CfSpaceLevelPage.detect().then(s => { + spacePage = s; + spacePage.waitForPageOrChildPage(); + spacePage.loadingIndicator.waitUntilNotShown(); + }); + + }); }); - }); }); } @@ -106,7 +116,7 @@ describe('CF - Space Level -', () => { }); describe('Basic Tests -', () => { - beforeEach(navToPage); + navToPage(); it('Breadcrumb', testBreadcrumb); @@ -134,7 +144,7 @@ describe('CF - Space Level -', () => { }); describe('Basic Tests -', () => { - beforeEach(navToPage); + navToPage(); it('Breadcrumb', testBreadcrumb); diff --git a/src/test-e2e/po/cf-users-list.po.ts b/src/test-e2e/po/cf-users-list.po.ts index e9f121aefd..b35193aae5 100644 --- a/src/test-e2e/po/cf-users-list.po.ts +++ b/src/test-e2e/po/cf-users-list.po.ts @@ -32,9 +32,7 @@ export class UserRoleChip extends ChipComponent { remove(): promise.Promise { this.getCross().click(); const confirm = new ConfirmDialogComponent(); - confirm.getMessage().then(message => { - expect(message).toContain(this.roleText); - }); + confirm.waitForMessage(this.roleText); confirm.confirm(); confirm.waitUntilNotShown('Confirmation dialog'); return this.waitUntilNotShown('User Role Chip still shown: ' + this.roleText); diff --git a/src/test-e2e/po/confirm-dialog.ts b/src/test-e2e/po/confirm-dialog.ts index dd99eea3eb..60792479b5 100644 --- a/src/test-e2e/po/confirm-dialog.ts +++ b/src/test-e2e/po/confirm-dialog.ts @@ -1,8 +1,10 @@ -import { by, element, promise } from 'protractor'; +import { browser, by, element, ElementFinder, promise, protractor } from 'protractor'; import { Component } from './component.po'; import { FormComponent } from './form.po'; +const until = protractor.ExpectedConditions; + export class DialogButton { index: number; label: string; @@ -69,15 +71,23 @@ export class ConfirmDialogComponent extends Component { return this.locator.element(by.className('confirm-dialog__header-title')).getText(); } + getMessageElement(): ElementFinder { + return this.locator.element(by.className('confirm-dialog__message')); + } + getMessage(): promise.Promise { - return this.locator.element(by.className('confirm-dialog__message')).getText(); + return this.getMessageElement().getText(); + } + + waitForMessage(message: string): promise.Promise { + return browser.wait(until.textToBePresentInElement(this.getMessageElement(), message), 5000); } // Get metadata for all of the fields in the form getButtons(): promise.Promise { return this.locator.all(by.tagName('button')).map((elm, index) => { return { - index: index, + index, label: elm.getText(), class: elm.getAttribute('class'), isWarning: elm.getAttribute('class').then(v => v.indexOf('mat-warn') >= 0), From b05f472ce4c81f7e918ef1fefd95f1db22ff3cf9 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Thu, 5 Dec 2019 13:37:34 +0000 Subject: [PATCH 29/29] Port downstream list free text search changes (#3981) * Port downstream list filter changes - https://github.com/SUSE/stratos/pull/185 - Apply to feature flag table * Fix e2e tests --- .../cf-feature-flags-data-source.ts | 31 ++++++++++++++++- .../cf-feature-flags-list-config.service.ts | 33 ++++++++++++++----- .../list-data-source.ts | 3 +- .../local-list-controller.ts | 1 + .../components/list/list.component.html | 21 ++++++++---- .../components/list/list.component.spec.ts | 17 +++++++++- .../shared/components/list/list.component.ts | 31 +++++++++++++++-- .../components/list/list.component.types.ts | 18 ++++++++-- .../store/src/actions/list.actions.ts | 1 + .../store/src/actions/pagination.actions.ts | 12 +++++++ ...agination-reducer-set-client-filter-key.ts | 19 +++++++++++ .../pagination-reducer/pagination.reducer.ts | 4 +++ .../store/src/types/pagination.types.ts | 1 + .../applications/application-wall-e2e.spec.ts | 2 +- src/test-e2e/po/list.po.ts | 6 ++-- 15 files changed, 174 insertions(+), 26 deletions(-) create mode 100644 src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-set-client-filter-key.ts diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-feature-flags/cf-feature-flags-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-feature-flags/cf-feature-flags-data-source.ts index f688dbe04e..8ef24d10e0 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-feature-flags/cf-feature-flags-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-feature-flags/cf-feature-flags-data-source.ts @@ -7,6 +7,7 @@ import { ListDataSource, } from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; import { IListConfig } from '../../../../../../../core/src/shared/components/list/list.component.types'; +import { PaginationEntityState } from '../../../../../../../store/src/types/pagination.types'; import { cfEntityFactory } from '../../../../../cf-entity-factory'; import { createCfFeatureFlagFetchAction } from './cf-feature-flags-data-source.helpers'; @@ -28,6 +29,9 @@ export const FeatureFlagDescriptions = { service_instance_sharing: 'Org and Space Managers can allow service instances to be shared across different spaces.' }; export class CfFeatureFlagsDataSource extends ListDataSource { + static nameColumnId = 'name'; + static descriptionColumnId = 'description'; + constructor(store: Store, cfGuid: string, listConfig?: IListConfig) { const action = createCfFeatureFlagFetchAction(cfGuid); super({ @@ -37,7 +41,32 @@ export class CfFeatureFlagsDataSource extends ListDataSource { getRowUniqueId: (ff) => ff.guid, paginationKey: action.paginationKey, isLocal: true, - transformEntities: [{ type: 'filter', field: 'name' }], + transformEntities: [ + ((entities: IFeatureFlag[], paginationState: PaginationEntityState) => { + if (!paginationState.clientPagination.filter.string) { + return entities; + } + + const filterString = paginationState.clientPagination.filter.string.toUpperCase(); + + const filterKey = paginationState.clientPagination.filter.filterKey; + + switch (filterKey) { + case CfFeatureFlagsDataSource.nameColumnId: + return entities.filter(ff => ff.name.toUpperCase().includes(filterString)); + case CfFeatureFlagsDataSource.descriptionColumnId: + return entities.filter(ff => { + const description = FeatureFlagDescriptions[ff.name]; + if (!description) { + return false; + } + return description.toUpperCase().includes(filterString); + }); + default: + return entities; + } + }) + ], listConfig }); } diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-feature-flags/cf-feature-flags-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-feature-flags/cf-feature-flags-list-config.service.ts index 3d165dd1f6..145e62713d 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-feature-flags/cf-feature-flags-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-feature-flags/cf-feature-flags-list-config.service.ts @@ -4,9 +4,8 @@ import { Store } from '@ngrx/store'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { IFeatureFlag } from '../../../../../../../core/src/core/cf-api.types'; import { ITableColumn } from '../../../../../../../core/src/shared/components/list/list-table/table.types'; -import { ListViewTypes } from '../../../../../../../core/src/shared/components/list/list.component.types'; +import { IListFilter, ListViewTypes } from '../../../../../../../core/src/shared/components/list/list.component.types'; import { ListView } from '../../../../../../../store/src/actions/list.actions'; -import { APIResource } from '../../../../../../../store/src/types/api.types'; import { ActiveRouteCfOrgSpace } from '../../../../../features/cloud-foundry/cf-page.types'; import { BaseCfListConfig } from '../base-cf/base-cf-list-config'; import { CfFeatureFlagsDataSource, FeatureFlagDescriptions } from './cf-feature-flags-data-source'; @@ -14,6 +13,12 @@ import { TableCellFeatureFlagStateComponent } from './table-cell-feature-flag-st @Injectable() export class CfFeatureFlagsListConfigService extends BaseCfListConfig { + + constructor(private store: Store, activeRouteCfOrgSpace: ActiveRouteCfOrgSpace) { + super(); + this.dataSource = new CfFeatureFlagsDataSource(this.store, activeRouteCfOrgSpace.cfGuid, this); + } + dataSource: CfFeatureFlagsDataSource; defaultView = 'table' as ListView; pageSizeOptions = [25, 50, 100]; @@ -27,7 +32,7 @@ export class CfFeatureFlagsListConfigService extends BaseCfListConfig> = [ { - columnId: 'name', + columnId: CfFeatureFlagsDataSource.nameColumnId, headerCell: () => 'Name', cellDefinition: { getValue: (row) => `${row.name}` @@ -41,7 +46,7 @@ export class CfFeatureFlagsListConfigService extends BaseCfListConfig 'Description', cellDefinition: { getValue: (row) => FeatureFlagDescriptions[row.name] @@ -61,10 +66,22 @@ export class CfFeatureFlagsListConfigService extends BaseCfListConfig, private activeRouteCfOrgSpace: ActiveRouteCfOrgSpace) { - super(); - this.dataSource = new CfFeatureFlagsDataSource(this.store, activeRouteCfOrgSpace.cfGuid, this); - } + + filters: IListFilter[] = [ + { + default: true, + key: CfFeatureFlagsDataSource.nameColumnId, + label: 'Name', + placeholder: 'Filter by Name' + }, + { + key: CfFeatureFlagsDataSource.descriptionColumnId, + label: 'Description', + placeholder: 'Filter by Description' + } + ]; + + getFilters = () => this.filters; getColumns = () => this.columns; getDataSource = () => this.dataSource; } diff --git a/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/list-data-source.ts b/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/list-data-source.ts index 3c6d823606..b2aec5ffd4 100644 --- a/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/list-data-source.ts +++ b/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/list-data-source.ts @@ -454,7 +454,8 @@ export abstract class ListDataSource extends DataSource implements return this.pagination$.pipe( map(pag => ({ string: this.isLocal ? pag.clientPagination.filter.string : this.getFilterFromParams(pag), - items: { ...pag.clientPagination.filter.items } + items: { ...pag.clientPagination.filter.items }, + filterKey: pag.clientPagination.filter.filterKey, })), tag('list-filter') ); diff --git a/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/local-list-controller.ts b/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/local-list-controller.ts index 766c881d4e..d89b4b4d34 100644 --- a/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/local-list-controller.ts +++ b/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/local-list-controller.ts @@ -134,6 +134,7 @@ export class LocalListController { + (paginationEntity.params['order-direction-field'] as string || '') + ',' + (paginationEntity.params['order-direction'] as string || '') + ',' + paginationEntity.clientPagination.filter.string + ',' + + paginationEntity.clientPagination.filter.filterKey + ',' + paginationEntity.forcedLocalPage + Object.values(paginationEntity.clientPagination.filter.items); // Some outlier cases actually fetch independently from this list (looking at you app variables) diff --git a/src/frontend/packages/core/src/shared/components/list/list.component.html b/src/frontend/packages/core/src/shared/components/list/list.component.html index e4fb2720d0..9c99bb672a 100644 --- a/src/frontend/packages/core/src/shared/components/list/list.component.html +++ b/src/frontend/packages/core/src/shared/components/list/list.component.html @@ -16,10 +16,8 @@
@@ -71,12 +69,23 @@ +
+ + Filter Selection + + + {{ filter.label }} + + + +
+ [disabled]="(filterColumns.length > 1 && filterSelected === undefined) || (dataSource.isLoadingPage$ | async)" + name="filter" placeholder="{{ filterSelected?.placeholder || config.text?.filter || 'Filter'}}">
diff --git a/src/frontend/packages/core/src/shared/components/list/list.component.spec.ts b/src/frontend/packages/core/src/shared/components/list/list.component.spec.ts index 9be658c736..ab5406c996 100644 --- a/src/frontend/packages/core/src/shared/components/list/list.component.spec.ts +++ b/src/frontend/packages/core/src/shared/components/list/list.component.spec.ts @@ -6,6 +6,7 @@ import { BehaviorSubject, of as observableOf } from 'rxjs'; import { switchMap } from 'rxjs/operators'; import { ListView } from '../../../../../store/src/actions/list.actions'; +import { GeneralAppState } from '../../../../../store/src/app-state'; import { APIResource } from '../../../../../store/src/types/api.types'; import { EndpointModel } from '../../../../../store/src/types/endpoint.types'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; @@ -20,7 +21,6 @@ import { EndpointListHelper } from './list-types/endpoint/endpoint-list.helpers' import { EndpointsListConfigService } from './list-types/endpoint/endpoints-list-config.service'; import { ListComponent } from './list.component'; import { ListConfig, ListViewTypes } from './list.component.types'; -import { InternalAppState, GeneralAppState } from '../../../../../store/src/app-state'; class MockedNgZone { run = fn => fn(); @@ -43,6 +43,7 @@ describe('ListComponent', () => { getInitialised: () => null, getMultiActions: () => null, getMultiFiltersConfigs: () => null, + getFilters: () => null, getSingleActions: () => null, isLocal: false, pageSizeOptions: [1], @@ -153,6 +154,7 @@ describe('ListComponent', () => { describe('Header', () => { it('Nothing enabled', () => { component.config.getMultiFiltersConfigs = () => []; + component.config.getFilters = () => []; component.config.enableTextFilter = false; component.config.viewType = ListViewTypes.CARD_ONLY; component.config.defaultView = 'card' as ListView; @@ -210,6 +212,19 @@ describe('ListComponent', () => { } ]; }; + component.config.getFilters = () => ([ + { + default: true, + key: 'a', + label: 'A', + placeholder: 'Filter by A' + }, + { + key: 'b', + label: 'B', + placeholder: 'Filter by B' + } + ]); component.config.enableTextFilter = true; component.config.viewType = ListViewTypes.CARD_ONLY; component.config.defaultView = 'card' as ListView; diff --git a/src/frontend/packages/core/src/shared/components/list/list.component.ts b/src/frontend/packages/core/src/shared/components/list/list.component.ts index f053beba0e..f84a300d18 100644 --- a/src/frontend/packages/core/src/shared/components/list/list.component.ts +++ b/src/frontend/packages/core/src/shared/components/list/list.component.ts @@ -42,7 +42,6 @@ import { withLatestFrom, } from 'rxjs/operators'; -import { CFAppState } from '../../../../../cloud-foundry/src/cf-app-state'; import { ListFilter, ListPagination, @@ -50,7 +49,8 @@ import { ListView, SetListViewAction, } from '../../../../../store/src/actions/list.actions'; -import { SetPage } from '../../../../../store/src/actions/pagination.actions'; +import { SetClientFilterKey, SetPage } from '../../../../../store/src/actions/pagination.actions'; +import { GeneralAppState } from '../../../../../store/src/app-state'; import { ActionState } from '../../../../../store/src/reducers/api-request-reducer/types'; import { getListStateObservables } from '../../../../../store/src/reducers/list.reducer'; import { entityCatalogue } from '../../../core/entity-catalogue/entity-catalogue.service'; @@ -69,13 +69,13 @@ import { defaultPaginationPageSizeOptionsTable, IGlobalListAction, IListConfig, + IListFilter, IMultiListAction, IOptionalAction, ListConfig, ListViewTypes, MultiFilterManager, } from './list.component.types'; -import { GeneralAppState } from '../../../../../store/src/app-state'; @Component({ selector: 'app-list', @@ -176,6 +176,8 @@ export class ListComponent implements OnInit, OnChanges, OnDestroy, AfterView value: null }; private sortColumns: ITableColumn[]; + private filterColumns: IListFilter[]; + private filterSelected: IListFilter; private paginationWidgetToStore: Subscription; private filterWidgetToStore: Subscription; @@ -396,8 +398,23 @@ export class ListComponent implements OnInit, OnChanges, OnDestroy, AfterView this.headerSort.direction = sort.direction; })); + this.filterColumns = this.config.getFilters ? this.config.getFilters() : []; + const filterStoreToWidget = this.paginationController.filter$.pipe(tap((paginationFilter: ListFilter) => { this.filterString = paginationFilter.string; + + const filterKey = paginationFilter.filterKey; + if (filterKey) { + this.filterSelected = this.filterColumns.find(filterConfig => { + return filterConfig.key === filterKey; + }); + } else if (this.filterColumns) { + this.filterSelected = this.filterColumns.find(filterConfig => filterConfig.default); + if (this.filterSelected) { + this.updateListFilter(this.filterSelected); + } + } + // Pipe store values to filter managers. This ensures any changes such as automatically selected orgs/spaces are shown in the drop // downs (change org to one with one space results in that space being selected) Object.values(this.multiFilterManagers).forEach((filterManager: MultiFilterManager, index: number) => { @@ -590,6 +607,14 @@ export class ListComponent implements OnInit, OnChanges, OnDestroy, AfterView }); } + updateListFilter(filterSelected: IListFilter) { + this.store.dispatch(new SetClientFilterKey( + this.dataSource, + this.dataSource.paginationKey, + filterSelected.key + )); + } + executeActionMultiple(listActionConfig: IMultiListAction) { const result = listActionConfig.action(Array.from(this.dataSource.selectedRows.values())); if (isObservable(result)) { diff --git a/src/frontend/packages/core/src/shared/components/list/list.component.types.ts b/src/frontend/packages/core/src/shared/components/list/list.component.types.ts index 4969e80f8d..fb42367cfb 100644 --- a/src/frontend/packages/core/src/shared/components/list/list.component.types.ts +++ b/src/frontend/packages/core/src/shared/components/list/list.component.types.ts @@ -28,11 +28,11 @@ export interface IListConfig { /** * List of actions that are presented as individual buttons when one or more rows are selected. For example `Delete` of selected rows. */ - getMultiActions: (schemaKey: string) => IMultiListAction[]; + getMultiActions: () => IMultiListAction[]; /** * List of actions that are presented in a mat-menu for an individual entity. For example `unmap` an application route */ - getSingleActions: (schemaKey: string) => IListAction[]; + getSingleActions: () => IListAction[]; /** * Collection of column definitions to show when the list is in table mode */ @@ -47,6 +47,12 @@ export interface IListConfig { * to the data sources transformEntities collection should be used to apply these custom settings to the data. */ getMultiFiltersConfigs: () => IListMultiFilterConfig[]; + /** + * Collection of filter definitions to support filtering across multiple fields in a list. + * When the filter is selected in a dropdown the filterString filters results using the chosen field. + * Combined with a transformEntities DataFunction that consumes the filterKey. + */ + getFilters?: () => IListFilter[]; /** * Fetch an observable that will emit once the underlying config components have been created. For instance if the data source requires * something from the store which requires an async call @@ -120,6 +126,13 @@ export interface IListMultiFilterConfig { select: BehaviorSubject; } +export interface IListFilter { + default?: boolean; + key: string; + label: string; + placeholder: string; +} + export interface IListMultiFilterConfigItem { label: string; item: any; @@ -144,6 +157,7 @@ export class ListConfig implements IListConfig { getColumns = (): ITableColumn[] => null; getDataSource = (): ListDataSource => null; getMultiFiltersConfigs = (): IListMultiFilterConfig[] => []; + getFilters = (): IListFilter[] => []; getInitialised = () => observableOf(true); } diff --git a/src/frontend/packages/store/src/actions/list.actions.ts b/src/frontend/packages/store/src/actions/list.actions.ts index 6937ceb634..8827fc0f36 100644 --- a/src/frontend/packages/store/src/actions/list.actions.ts +++ b/src/frontend/packages/store/src/actions/list.actions.ts @@ -20,6 +20,7 @@ export class ListFilter { items: { [key: string]: any; }; + filterKey?: string; } export const ListStateActionTypes = { diff --git a/src/frontend/packages/store/src/actions/pagination.actions.ts b/src/frontend/packages/store/src/actions/pagination.actions.ts index da5b74cad6..17355a380b 100644 --- a/src/frontend/packages/store/src/actions/pagination.actions.ts +++ b/src/frontend/packages/store/src/actions/pagination.actions.ts @@ -16,6 +16,7 @@ export const SET_RESULT_COUNT = '[Pagination] Set result count'; export const SET_CLIENT_PAGE_SIZE = '[Pagination] Set client page size'; export const SET_CLIENT_PAGE = '[Pagination] Set client page'; export const SET_CLIENT_FILTER = '[Pagination] Set client filter'; +export const SET_CLIENT_FILTER_KEY = '[Pagination] Set client filter key'; export const SET_PARAMS = '[Pagination] Set Params'; export const SET_INITIAL_PARAMS = '[Pagination] Set initial params'; export const ADD_PARAMS = '[Pagination] Add Params'; @@ -136,6 +137,17 @@ export class SetClientFilter extends BasePaginationAction implements Action { type = SET_CLIENT_FILTER; } +export class SetClientFilterKey extends BasePaginationAction implements Action { + constructor( + pEntityConfig: Partial, + public paginationKey: string, + public filterKey: string, + ) { + super(pEntityConfig); + } + type = SET_CLIENT_FILTER_KEY; +} + export class SetParams extends BasePaginationAction implements Action { constructor( pEntityConfig: Partial, diff --git a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-set-client-filter-key.ts b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-set-client-filter-key.ts new file mode 100644 index 0000000000..75d31ac45f --- /dev/null +++ b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-set-client-filter-key.ts @@ -0,0 +1,19 @@ +import { SetClientFilterKey } from '../../actions/pagination.actions'; +import { PaginationEntityState } from '../../types/pagination.types'; +import { spreadClientPagination } from './pagination-reducer.helper'; + +export function paginationSetClientFilterKey(state: PaginationEntityState, action: SetClientFilterKey) { + const clientPagination = spreadClientPagination(state.clientPagination); + + return { + ...state, + error: false, + clientPagination: { + ...clientPagination, + filter: { + ...clientPagination.filter, + filterKey: action.filterKey, + } + } + }; +} diff --git a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination.reducer.ts b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination.reducer.ts index f8f19d8615..c6196cc84f 100644 --- a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination.reducer.ts +++ b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination.reducer.ts @@ -16,6 +16,7 @@ import { REMOVE_PARAMS, RESET_PAGINATION, SET_CLIENT_FILTER, + SET_CLIENT_FILTER_KEY, SET_CLIENT_PAGE, SET_CLIENT_PAGE_SIZE, SET_INITIAL_PARAMS, @@ -38,6 +39,7 @@ import { paginationMaxReached } from './pagination-reducer-max-reached'; import { paginationRemoveParams } from './pagination-reducer-remove-params'; import { getDefaultPaginationEntityState, paginationResetPagination } from './pagination-reducer-reset-pagination'; import { paginationSetClientFilter } from './pagination-reducer-set-client-filter'; +import { paginationSetClientFilterKey } from './pagination-reducer-set-client-filter-key'; import { paginationSetClientPage } from './pagination-reducer-set-client-page'; import { paginationSetClientPageSize } from './pagination-reducer-set-client-page-size'; import { paginationSetPage } from './pagination-reducer-set-page'; @@ -76,6 +78,8 @@ const getPaginationUpdater = (types: [string, string, string]) => { return paginationSetClientPage(state, action); case SET_CLIENT_FILTER: return paginationSetClientFilter(state, action); + case SET_CLIENT_FILTER_KEY: + return paginationSetClientFilterKey(state, action); case SET_PAGE_BUSY: return paginationPageBusy(state, action); default: diff --git a/src/frontend/packages/store/src/types/pagination.types.ts b/src/frontend/packages/store/src/types/pagination.types.ts index 4beb06b2e9..b73d631f07 100644 --- a/src/frontend/packages/store/src/types/pagination.types.ts +++ b/src/frontend/packages/store/src/types/pagination.types.ts @@ -16,6 +16,7 @@ export interface PaginationClientFilter { items: { [key: string]: any; }; + filterKey?: string; } export interface PaginationClientPagination { diff --git a/src/test-e2e/applications/application-wall-e2e.spec.ts b/src/test-e2e/applications/application-wall-e2e.spec.ts index 669d247931..e28265cf62 100644 --- a/src/test-e2e/applications/application-wall-e2e.spec.ts +++ b/src/test-e2e/applications/application-wall-e2e.spec.ts @@ -125,7 +125,7 @@ describe('Application Wall Tests -', () => { function createPageSizeSelectId(): string { // The ctrl is hidden inside a mat-paginator, the id will change depending on other selects on page - return hasCfFilter ? 'mat-select-4' : 'mat-select-3'; + return hasCfFilter ? 'mat-select-5' : 'mat-select-4'; } function tearDown(orgName: string) { diff --git a/src/test-e2e/po/list.po.ts b/src/test-e2e/po/list.po.ts index 27a10e1470..4ef50b5227 100644 --- a/src/test-e2e/po/list.po.ts +++ b/src/test-e2e/po/list.po.ts @@ -39,7 +39,7 @@ export class ListTableComponent extends Component { ).first(); browser.wait(until.presenceOf(cell)); - return cell.element(by.xpath('ancestor::app-table-row'));; + return cell.element(by.xpath('ancestor::app-table-row')); } // Get the data in the table @@ -385,11 +385,11 @@ export class ListPaginationComponent extends Component { } getPageSize(customCtrlName?: string): promise.Promise { - return this.getPageSizeForm().getText(customCtrlName || 'mat-select-1'); + return this.getPageSizeForm().getText(customCtrlName || 'mat-select-2'); } setPageSize(pageSize, customCtrlName?: string): promise.Promise { - const name = customCtrlName || 'mat-select-1'; + const name = customCtrlName || 'mat-select-2'; return this.getPageSizeForm().fill({ [name]: pageSize }); }