diff --git a/.gitignore b/.gitignore index 8682767ec2a5ac50995b767f6e165df96f6cacb0..47c3a50c12a4aa68932408e86c6c11dfbfb3ef23 100644 --- a/.gitignore +++ b/.gitignore @@ -15,13 +15,11 @@ # Ignore files created during CI /test/group_vars/all/ /test/inventory* -/test/behave/behave.ini -/test/behave/rerun_failing.features /clusters /install/installation-kustomization/*.txt # Ignore files created during tests -/test/behave/**/screenshots/ +Screenshot-*.png # Etc __pycache__ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 218423a5beaa925901b97cc679e3b1de3f051823..bd4ec365d2c6c10893f52d188fda94289765d17f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -94,12 +94,12 @@ include: - flux2/apps/$APP/*.yaml - flux2/cluster/optional/$APP/*.yaml - install/install-${APP}.sh - - test/behave/features/$APP.feature + - test/taiko/* - if: '$TRIGGER_JOBS =~ /enable-nextcloud/' - if: '$CI_COMMIT_MESSAGE =~ /TRIGGER_JOBS=.*enable-nextcloud/' - if: '$CI_COMMIT_BRANCH == "master"' -.prometheus_stack_rules: +.kube_prometheus_stack_rules: extends: - .general_rules @@ -117,7 +117,7 @@ include: - flux2/apps/$APP/*.yaml - flux2/cluster/optional/$APP/*.yaml - install/install-${APP}.sh - - test/behave/features/$APP.feature + - test/taiko/* - if: '$TRIGGER_JOBS =~ /enable-rocketchat/' - if: '$CI_COMMIT_MESSAGE =~ /TRIGGER_JOBS=.*enable-rocketchat/' - if: '$CI_COMMIT_BRANCH == "master"' @@ -127,7 +127,7 @@ include: - changes: - flux2/core/base/$APP/*.yaml - install/install-openappstack.sh - - test/behave/features/$APP.feature + - test/taiko/* - if: '$TRIGGER_JOBS =~ /enable-single-sign-on/' - if: '$CI_COMMIT_MESSAGE =~ /TRIGGER_JOBS=.*enable-single-sign-on/' - if: '$CI_COMMIT_BRANCH == "master"' @@ -138,7 +138,7 @@ include: - flux2/apps/$APP/*.yaml - flux2/cluster/optional/$APP/*.yaml - install/install-${APP}.sh - - test/behave/features/$APP.feature + - test/taiko/* - if: '$TRIGGER_JOBS =~ /enable-wordpress/' - if: '$CI_COMMIT_MESSAGE =~ /TRIGGER_JOBS=.*enable-wordpress/' - if: '$CI_COMMIT_BRANCH == "master"' @@ -415,7 +415,7 @@ kube-prometheus-stack-helm-release: APP: "kube-prometheus-stack" extends: - .base-helm-release - - .prometheus_stack_rules + - .kube_prometheus_stack_rules single-sign-on-helm-release: variables: @@ -591,7 +591,7 @@ kube-prometheus-stack-ready: - job: setup-openappstack extends: - .apps-ready - - .prometheus_stack_rules + - .kube_prometheus_stack_rules rocketchat-ready: variables: @@ -656,7 +656,7 @@ kube-prometheus-stack-cert: - job: setup-openappstack extends: - .apps-cert - - .prometheus_stack_rules + - .kube_prometheus_stack_rules rocketchat-cert: variables: @@ -708,7 +708,7 @@ testinfra: kube-prometheus-stack-alerts: stage: health-test variables: - # APP var is used in job specific rules (i.e. .prometheus_stack_rules) + # APP var is used in job specific rules (i.e. .kube_prometheus_stack_rules) APP: "kube-prometheus-stack" allow_failure: true script: @@ -717,7 +717,7 @@ kube-prometheus-stack-alerts: - pytest -s -m 'prometheus' --connection=ansible --ansible-inventory=${CLUSTER_DIR}/inventory.yml --hosts='ansible://*' extends: - .ssh_setup - - .prometheus_stack_rules + - .kube_prometheus_stack_rules needs: - job: kube-prometheus-stack-ready - job: setup-openappstack @@ -727,63 +727,62 @@ kube-prometheus-stack-alerts: # Stage: integration-test # ======================= # -# Runs integration tests for most apps using behave +# Runs integration tests for most apps using taiko -.behave: +.taiko: stage: integration-test script: - *debug_information - # Run the behave tests for specific app - - python3 -m openappstack $HOSTNAME test --behave-headless --behave-ignore-certificate-errors --behave-tags $APP || python3 -m openappstack $HOSTNAME test --behave-headless --behave-ignore-certificate-errors --behave-tags $APP --behave-rerun-failing + # Run the taiko tests for specific app + - python3 -m openappstack $HOSTNAME test --apps $APP retry: 2 artifacts: paths: - - test/behave/screenshots/ + - test/taiko/Screenshot* expire_in: 1 month when: on_failure extends: - .ssh_setup interruptible: true -kube-prometheus-stack-behave: +grafana-taiko: variables: - APP: "kube-prometheus-stack" + APP: "grafana" needs: - job: kube-prometheus-stack-cert - job: setup-openappstack extends: - - .behave - - .prometheus_stack_rules + - .taiko + - .kube_prometheus_stack_rules -nextcloud-behave: +nextcloud-taiko: variables: APP: "nextcloud" needs: - job: nextcloud-cert - job: setup-openappstack extends: - - .behave + - .taiko - .nextcloud_rules -rocketchat-behave: +rocketchat-taiko: variables: APP: "rocketchat" needs: - job: rocketchat-cert - job: setup-openappstack extends: - - .behave + - .taiko - .rocketchat_rules - -wordpress-behave: +wordpress-taiko: variables: APP: "wordpress" needs: - job: wordpress-cert - job: setup-openappstack extends: - - .behave + - .taiko - .wordpress_rules diff --git a/.gitlab/ci_templates/kaniko.yml b/.gitlab/ci_templates/kaniko.yml index 93dfd3654795e98931f1a516db2babfa15291ee6..fd5b379183ee25490ad42a28142da615376f7c8a 100644 --- a/.gitlab/ci_templates/kaniko.yml +++ b/.gitlab/ci_templates/kaniko.yml @@ -17,7 +17,7 @@ image: # We need a shell to provide the registry credentials, so we need to use the # kaniko debug image (https://github.com/GoogleContainerTools/kaniko#debug-image) - name: gcr.io/kaniko-project/executor:debug + name: gcr.io/kaniko-project/executor:v1.6.0-debug entrypoint: [""] script: - mkdir -p /kaniko/.docker/ diff --git a/.gitlab/issue_templates/new_app.md b/.gitlab/issue_templates/new_app.md index 3bdfdbfcd352b1646bbdf83bf361939de2292886..f9bfe0bd6739ca9ea1f2d3e0221b59af0b67241f 100644 --- a/.gitlab/issue_templates/new_app.md +++ b/.gitlab/issue_templates/new_app.md @@ -17,7 +17,7 @@ ## Tests -* [ ] Add behave feature (`tests/behave/feature`) +* [ ] Add taiko test (`tests/taiko`) * [ ] Check for successful helmrelease (`test/pytest/test_helmreleases.py`) * [ ] Test cert (`test/pytest/test_certs.py`) diff --git a/Dockerfile b/Dockerfile index 56a5130d85b3211677c47d3c29b5c5e3ef4f9708..4d8c86aac25af10ed3d256a552c895283d4490b1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,12 @@ FROM alpine:3.13.5 LABEL name="OpenAppStack management" -LABEL version="4.3" +LABEL version="4.3.1" LABEL vendor1="Greenhost" +ENV TAIKO_SKIP_CHROMIUM_DOWNLOAD=true +ENV TAIKO_BROWSER_PATH=/usr/bin/chromium-browser +ENV TAIKO_BROWSER_ARGS=--no-sandbox,--start-maximized,--disable-dev-shm-usage,--ignore-certificate-errors # Download yq v4 from github until it's packaged in alpine > 3.13 ADD https://github.com/mikefarah/yq/releases/download/v4.7.0/yq_linux_amd64 /usr/local/bin/yq @@ -17,7 +20,6 @@ RUN \ bash=5.1.0-r0 \ cargo=~1.47.0-r2 \ chromium=~86.0.4240.111-r0 \ - chromium-chromedriver=~86.0.4240.111-r0 \ curl=~7.77.0-r1 \ # needed for installing pycurl python module curl-dev=~7.77.0-r1 \ @@ -26,14 +28,18 @@ RUN \ libffi-dev=3.3-r2 \ make=~4.3-r0 \ musl-dev=~1.2.2-r1 \ + npm=~14.16.1-r1 \ openssh-client=~8.4_p1-r3 \ py3-pip=~20.3.4-r0 \ py3-wheel=~0.36.2-r0 \ python3-dev=~3.8.10-r0 \ rsync=~3.2.3-r1 \ rust=~1.47.0-r2 && \ + rm -rf /var/cache/* && \ + mkdir /var/cache/apk && \ chmod a+x /usr/local/bin/* && \ update-ca-certificates && \ pip install --no-cache-dir -r /requirements.txt && \ ln -s /usr/bin/python3 /usr/bin/python && \ - tar -xzf /tmp/flux*.tar.gz && mv ./flux /usr/local/bin + tar -xzf /tmp/flux*.tar.gz && mv ./flux /usr/local/bin && \ + npm install -g taiko@1.2.5 diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index bee86ba4153a6c0ae3303e4a30fa8d8bb0fb8a45..a71096c5169d6406db19bd182c5903534a65c731 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -17,7 +17,7 @@ To get an overall status of your cluster you can run the tests from the command line. There are two types of tests: [testinfra](https://testinfra.readthedocs.io/en/latest/) -tests, and [behave](https://behave.readthedocs.io/en/latest/) tests. +tests, and [taiko](https://taiko.dev) tests. ### Testinfra tests @@ -64,37 +64,32 @@ Test a specific application: not verified. Therefore we need to force plain ssh:// with either `connection=ssh` or `--hosts=ssh://…` -### Behave tests +### Taiko tests -Behave tests run in a browser and test if all the interfaces are up +Taiko tests run in a browser and test if all the interfaces are up and running and correctly connected to each other. They are integrated in the `openappstack` CLI command suite. #### Prerequisites -By default the behave tests use the Chromium browser and Chromium webdriver. If -you want/need to use the Firefox webdriver please refer to the manual behave -test instructions below, under [Advanced Usage](#advanced-usage). +Install [taiko](https://taiko.dev): -Install [Chromedriver](https://sites.google.com/a/chromium.org/chromedriver/), -i.e. for Debian/Ubuntu use: - - apt install chromium-chromedriver + npm install -g taiko #### Usage -To run all behave tests, run the following command in this repository: +To run all taiko tests, run the following command in this repository: python -m openappstack CLUSTERNAME test -In the future, this command will run all tests, but now only *behave* is +In the future, this command will run all tests, but now only *taiko* is implemented. To learn more about the `test` subcommand, run: python -m openappstack CLUSTERNAME test --help -You can also only run a behave test for a specific application, i.e.: +You can also only run a taiko test for a specific application, i.e.: - python -m openappstack CLUSTERNAME test --behave-tags nextcloud + python -m openappstack CLUSTERNAME test --taiko-tags nextcloud ### Advanced usage @@ -133,42 +128,22 @@ then: gitlab-runner exec docker --env CI_REGISTRY_IMAGE="$CI_REGISTRY_IMAGE" --env SSH_PRIVATE_KEY="$SSH_PRIVATE_KEY" --env COSMOS_API_TOKEN="$COSMOS_API_TOKEN" bootstrap -#### Behave tests - -##### Using Firefox instead of Chromium - -If you want to use Firefox instead of Chromium, you need to install the gecko -driver - - apt install firefox-geckodriver - -Now you only need to add `-D browser=firefox` to the behave command line -options, so run: +#### Taiko tests - python -m openappstack CLUSTER_NAME test --behave-param='-D browser=firefox' +##### Using Taiko without the OpenAppStack CLI -##### Using behave without the OpenAppStack CLI - -Go to the `test/behave` directory and run: +Go to the `test/taiko` directory and run: For nextcloud & onlyoffice tests: - behave -D nextcloud.url=https://files.example.openappstack.net \ - -D nextcloud.password="$(cat ../../clusters/YOUR_CLUSTERNAME/secrets/nextcloud_admin_password)" \ - -t nextcloud - -You can replace `nextcloud` with `grafana` or `rocketchat` to test the other -applications. - -#### Run behave tests in openappstack-ci docker image - - docker run --rm -it open.greenhost.net:4567/openappstack/openappstack/openappstack-ci sh + export DOMAIN='oas.example.net' + export SSO_USERNAME='user1' + export SSO_USER_PW='...' + export TAIKO_TESTS='nextcloud' + taiko --observe taiko-tests.js - apk --no-cache add git - git clone https://open.greenhost.net/openappstack/openappstack.git - cd openappstack/test/behave - behave -D nextcloud.url=https://files.ci-20410.ci.openappstack.net \ - -D nextcloud.admin.password=… +You can replace `nextcloud` with `grafana` or `wordpress` to test the other +applications, or with `all` to test all applications. ## SSH access diff --git a/flux2/apps/monitoring/loki-release.yaml b/flux2/apps/monitoring/loki-release.yaml index df4409e1097c828992d3718fd05247cae9907b7c..07769a71ece2b4d99a8fe04a87bbf4ab732c2460 100644 --- a/flux2/apps/monitoring/loki-release.yaml +++ b/flux2/apps/monitoring/loki-release.yaml @@ -8,6 +8,7 @@ spec: releaseName: loki chart: spec: + # https://artifacthub.io/packages/helm/grafana/loki chart: loki version: 2.5.0 sourceRef: @@ -16,6 +17,7 @@ spec: namespace: flux-system interval: 40m values: + # https://github.com/grafana/helm-charts/blob/main/charts/loki/values.yaml resources: limits: cpu: 800m @@ -30,7 +32,14 @@ spec: size: 10Gi annotations: {} # existingClaim: + # https://github.com/grafana/helm-charts/blob/main/charts/loki/values.yaml#L46 config: + # https://github.com/grafana/helm-charts/blob/main/charts/loki/values.yaml#L48 + ingester: + chunk_idle_period: 30m + chunk_block_size: 1048576 + chunk_retain_period: 15m + max_transfer_retries: 10 # https://github.com/grafana/loki/blob/main/cmd/loki/loki-local-config.yaml # https://grafana.com/docs/loki/latest/operations/storage/retention schema_config: diff --git a/install/install-openappstack.sh b/install/install-openappstack.sh index 0b0efe56a6deb9dcff08900589bcef5854597331..2071c12371223920db04614dc7b2deec91124d89 100755 --- a/install/install-openappstack.sh +++ b/install/install-openappstack.sh @@ -7,9 +7,15 @@ flux install \ --watch-all-namespaces=true \ --namespace=flux-system +# get current git branch name +branch=$CI_COMMIT_REF_NAME +[ -z "$branch" ] && branch=$(git rev-parse --abbrev-ref HEAD) + +echo "Tracking branch $branch for https://open.greenhost.net/openappstack/openappstack flux repo" + flux create source git openappstack \ --url=https://open.greenhost.net/openappstack/openappstack \ - --branch=master \ + --branch=$branch \ --interval=1m flux create kustomization openappstack \ diff --git a/openappstack/__main__.py b/openappstack/__main__.py index 98ecdbd38261dc989c4f82b77301d7e418bbf7c1..56ac73998ff94ebd4ba7d82bc4b56370f06dc933 100755 --- a/openappstack/__main__.py +++ b/openappstack/__main__.py @@ -23,12 +23,10 @@ import logging from math import floor import os import sys +from subprocess import Popen, PIPE import greenhost_cloud -from behave.__main__ import main as behave_main from openappstack import name, cluster, ansible -ALL_TESTS = ['behave'] - # We're limiting to 50, because we use subdomains, the current longest of which # is 7 characters (`office.`). Max CN length is 63 characters, so we have some # wiggle room for longer subdomains in the future. @@ -210,41 +208,18 @@ def main(): # pylint: disable=too-many-statements,too-many-branches,too-many-lo test_parser.set_defaults(func=test) test_parser.add_argument( - '--run-test', - action='append', - help=('Run only a specific kind of test. If not provided, all tests ' - 'are run. The currently available tests are: ') \ - + ','.join(ALL_TESTS)) - - test_parser.add_argument( - '--behave-rerun-failing', - action='store_true', - help=('Run behave with @rerun_failing.features')) - test_parser.add_argument( - '--behave-tags', - nargs='+', - default=[], - help=('Only run behave tests with these tags')) - test_parser.add_argument( - '--behave-headless', - action='store_true', - help=('Run behave in headless mode')) - test_parser.add_argument( - '--behave-ignore-certificate-errors', + '--observe', action='store_true', - help=('Ignore certificate errors, needed if you use i.e. the Letsencrypt ACME staging CA')) + help=('Enables headful mode and runs script with 3000ms delay.')) test_parser.add_argument( - '--behave-param', - metavar=['PARAM[=VALUE]'], - action='append', - nargs=1, - default=[], - help=('Pass additional behave options (like "-D browser=firefox"')) + '--apps', + default='all', + help=('Which apps to test (i.e. "nextcloud,wordpress". Defaults to' + '"all".')) info_parser = subparsers.add_parser( 'info', help=("Show information about a cluster")) - info_parser.set_defaults(func=info) info_parser.add_argument( '--ip-address', @@ -252,6 +227,11 @@ def main(): # pylint: disable=too-many-statements,too-many-branches,too-many-lo help=('Only output the IP address of the machine') ) + secrets_parser = subparsers.add_parser( + 'secrets', + help=("Show OAS cluster secrets")) + secrets_parser.set_defaults(func=secrets) + args = parser.parse_args() loglevel = logging.DEBUG if args.verbose else logging.INFO init_logging(log, loglevel) @@ -288,6 +268,16 @@ def info(clus, args): clus.print_info(args) +def secrets(clus, args): + """ + Dumps cluster secrets and then exits + + :param cluster.Cluster clus: cluster to show information about + """ + clus.load_data() + clus.dump_secrets(args) + + def create(clus, args): # pylint: disable=too-many-branches """ Parses arguments for the 'create' subcommand @@ -398,52 +388,86 @@ def install(clus, args): def test(clus, args): """ - Runs behave or testinfra test. Overwrites behave_path/behave.ini! + Runs taiko tests. :param cluster.Cluster clus: Cluster object to run tests on :param argparse.Namespace args: Command line arguments """ - # At the moment we only have one type if test, but more tests will be added - # to this in the future. If args.run_test is empty, we run all the tests - run_test = args.run_test - if not run_test: - run_test = ALL_TESTS - - if 'behave' in run_test: - behave_path = os.path.join(os.path.dirname(__file__), '..', 'test', - 'behave') - # Run from the behave directory so behave automatically loads all the - # necessary files - os.chdir(behave_path) - clus.load_data() - behave_ini = os.path.join(behave_path, 'behave.ini') - clus.write_behave_config(behave_ini) - command = [] - if args.behave_rerun_failing: - command.append('@rerun_failing.features') - if args.behave_headless: - command.append('-D headless=True') - if args.behave_ignore_certificate_errors: - command.append('-D ignore_certificate_errors=True') - for tag in args.behave_tags: - log.info(command) - command.append('-t {tag}'.format(tag=tag)) - if args.behave_param: - for param in args.behave_param: - if len(param) > 1: - log.warning('More than 1 parameter. Ignoring the rest! ' - 'Use --behave-param several times to supply ' - 'more than 1 parameter') - param = param[0] - command.append(param) - log.info('Running behave command %s', command) - return_code = behave_main(command) - - # Remove behave.ini so we don't leave secrets hanging around. - os.remove(behave_ini) - - sys.exit(return_code) + taiko_path = os.path.join(os.path.dirname(__file__), '..', 'test', 'taiko') + # Run from the taiko directory so taiko automatically loads all the + # necessary files + os.chdir(taiko_path) + clus.load_data() + command = ['taiko'] + if args.observe: + command.append('--observe') + command.append('apps.js') + + # Set env vars + + # SSO tests currently only work with valid letsencrypt production certs. + # Therefor we disable SSO tests for now, until + # https://open.greenhost.net/openappstack/single-sign-on/-/issues/62 + # is fixed. + # + # sso_username = clus.get_password_from_kubernetes( + # 'oas-single-sign-on-variables', + # 'userbackend_admin_username', + # 'flux-system' + # ) + # sso_password = clus.get_password_from_kubernetes( + # 'oas-single-sign-on-variables', + # 'userbackend_admin_password', + # 'flux-system' + # ) + # os.environ["SSO_USERNAME"] = sso_username + # os.environ["SSO_PASSWORD"] = sso_password + + if "nextcloud" in args.apps or args.apps=='all': + nextcloud_password = clus.get_password_from_kubernetes( + 'oas-nextcloud-variables', + 'nextcloud_password', + 'flux-system' + ) + os.environ["NEXTCLOUD_PASSWORD"] = nextcloud_password + + if "rocketchat" in args.apps or args.apps=='all': + rocketchat_password = clus.get_password_from_kubernetes( + 'oas-rocketchat-variables', + 'rocketchat_admin_password', + 'flux-system' + ) + os.environ["ROCKETCHAT_PASSWORD"] = rocketchat_password + + if "wordpress" in args.apps or args.apps=='all': + wordpress_password = clus.get_password_from_kubernetes( + 'oas-wordpress-variables', + 'wordpress_admin_password', + 'flux-system' + ) + os.environ["WORDPRESS_PASSWORD"] = wordpress_password + + if "grafana" in args.apps or args.apps=='all': + grafana_password = clus.get_password_from_kubernetes( + 'oas-kube-prometheus-stack-variables', + 'grafana_admin_password', + 'flux-system' + ) + os.environ["GRAFANA_PASSWORD"] = grafana_password + + os.environ["TAIKO_TESTS"] = args.apps + os.environ['DOMAIN'] = clus.domain + + log.info('Running taiko command %s', command) + + with Popen(command, stdout=PIPE, bufsize=1, + universal_newlines=True) as taiko: + for line in taiko.stdout: + print(line, end='') + + return_code = (taiko.returncode) + sys.exit(return_code) def create_domain_records(domain, droplet_ip, subdomain=None): """ diff --git a/openappstack/cluster.py b/openappstack/cluster.py index 3a15b99debf3abfc422fc65bb69c8163702c3ea1..71425bea89ee2aa4205891247a2265e316562193 100644 --- a/openappstack/cluster.py +++ b/openappstack/cluster.py @@ -257,76 +257,46 @@ KUBECONFIG={secret_dir}/kube_config_cluster.yml """Path where all the passwords for cluster admins are saved""" return os.path.join(self.cluster_dir, 'secrets') - def write_behave_config(self, config_path): + def dump_secrets(self, args): """ - Write behave config file for the cluster. - - :param str config_path: Configuration is written to config_path (e.g. - /home/you/openappstack/test/behave.ini). If config_path already - exists, the program is aborted. + Shows all OAS cluster secrets. """ - if os.path.isfile(config_path): - log.error('%s file already exists, not overwriting ' - 'file! Remove the file if you want to run behave. ' - 'Program will exit now', config_path) - sys.exit(2) - - grafana_admin_password = self.get_password_from_kubernetes( - 'kube-prometheus-stack-grafana', - 'admin-password', - 'oas') - - rocketchat_admin_password = self.get_password_from_kubernetes( - 'oas-rocketchat-variables', - 'rocketchat_admin_password', - 'flux-system') - - nextcloud_admin_password = self.get_password_from_kubernetes( - 'nc-nextcloud', - 'nextcloud-password', - 'oas-apps') - - wordpress_admin_password = self.get_password_from_kubernetes( - 'oas-wordpress-variables', - 'wordpress_admin_password', - 'flux-system') - - behave_config = configparser.ConfigParser() - behave_config['behave'] = {} - behave_config['behave']['format'] = 'rerun' - behave_config['behave']['outfiles'] = 'rerun_failing.features' - behave_config['behave']['show_skipped'] = 'false' - - behave_config['behave.userdata'] = {} - - behave_config['behave.userdata']['nextcloud.url'] = \ - 'https://files.{}'.format(self.domain) - behave_config['behave.userdata']['nextcloud.username'] = 'admin' - behave_config['behave.userdata']['nextcloud.password'] = \ - nextcloud_admin_password - behave_config['behave.userdata']['onlyoffice.url'] = \ - 'https://office.{}/welcome'.format(self.domain) - - behave_config['behave.userdata']['rocketchat.url'] = \ - 'https://chat.{}'.format(self.domain) - behave_config['behave.userdata']['rocketchat.username'] = 'admin' - behave_config['behave.userdata']['rocketchat.password'] = \ - rocketchat_admin_password - - behave_config['behave.userdata']['wordpress.url'] = \ - 'https://www.{}/wp-login.php'.format(self.domain) - behave_config['behave.userdata']['wordpress.username'] = 'admin' - behave_config['behave.userdata']['wordpress.password'] = \ - wordpress_admin_password - - behave_config['behave.userdata']['grafana.url'] = \ - 'https://grafana.{}'.format(self.domain) - behave_config['behave.userdata']['grafana.username'] = 'admin' - behave_config['behave.userdata']['grafana.password'] = \ - grafana_admin_password - - with open(config_path, 'w') as config_file: - behave_config.write(config_file) + + all_secrets = { + 'flux-system': { + 'oas-kube-prometheus-stack-variables': ['grafana_admin_password'], + 'oas-nextcloud-variables': [ + 'nextcloud_mariadb_password', + 'nextcloud_mariadb_root_password', + 'nextcloud_password', + 'onlyoffice_jwt_secret', + 'onlyoffice_postgresql_password', + 'onlyoffice_rabbitmq_password'], + 'oas-rocketchat-variables': [ + 'rocketchat_admin_password', + 'mongodb_root_password', + 'mongodb_password' ], + 'oas-single-sign-on-variables': [ + 'userbackend_admin_username', + 'userbackend_admin_password', + 'userbackend_postgres_password', + 'hydra_system_secret' ], + 'oas-wordpress-variables': [ + 'wordpress_admin_password', + 'wordpress_mariadb_password', + 'wordpress_mariadb_root_password' ] + } + + } + + for sec in all_secrets.values(): + for app, app_secrets in sec.items(): + for app_secret in app_secrets: + secret = self.get_password_from_kubernetes( + app, + app_secret, + namespace) + print(app_secret + '=' + secret ) def get_password_from_kubernetes(self, secret, key, namespace): """ diff --git a/requirements.in b/requirements.in index ce21c81430aa32ef88eecb355f0ec37f9fccf6e7..387e00d4dced1d5074279f822024445047f19719 100644 --- a/requirements.in +++ b/requirements.in @@ -22,5 +22,4 @@ tld>=0.12.5 setuptools>=40.6.2 wheel>=0.33.1 -e git+https://open.greenhost.net/greenhost/cloud-api#egg=greenhost_cloud --e git+https://open.greenhost.net/openappstack/oas_behave@c05009a#egg=oas_behave passlib>=1.7.2 diff --git a/requirements.txt b/requirements.txt index d3bdb09e1901a6c433812a481a8ddce1d9a890e7..37719e0d57399159432edebb2e9f33c96a26c9c1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,18 +6,12 @@ # -e git+https://open.greenhost.net/greenhost/cloud-api#egg=greenhost_cloud # via -r requirements.in --e git+https://open.greenhost.net/openappstack/oas_behave@c05009a#egg=oas_behave - # via -r requirements.in ansible==2.9.18 # via -r requirements.in attrs==20.3.0 # via pytest bcrypt==3.2.0 # via paramiko -behave-webdriver==0.3.0 - # via oas-behave -behave==1.2.6 - # via behave-webdriver cachetools==4.2.1 # via google-auth certifi==2020.12.5 @@ -60,12 +54,6 @@ packaging==20.9 # via pytest paramiko==2.7.2 # via -r requirements.in -parse-type==0.5.2 - # via behave -parse==1.19.0 - # via - # behave - # parse-type passlib==1.7.4 # via -r requirements.in pluggy==0.13.1 @@ -119,16 +107,12 @@ ruamel.yaml.clib==0.2.2 # via ruamel.yaml ruamel.yaml==0.16.13 # via openshift -selenium==3.141.0 - # via behave-webdriver six==1.15.0 # via # bcrypt - # behave # google-auth # kubernetes # openshift - # parse-type # pynacl # pyopenssl # python-dateutil @@ -145,7 +129,6 @@ urllib3==1.26.4 # via # kubernetes # requests - # selenium websocket-client==0.58.0 # via kubernetes wheel==0.36.2 diff --git a/test/behave/features/environment.py b/test/behave/features/environment.py deleted file mode 100644 index 2cdc9f865c58d2f652a072702a4ce6547ff77ab2..0000000000000000000000000000000000000000 --- a/test/behave/features/environment.py +++ /dev/null @@ -1,18 +0,0 @@ -from oas_behave.environment import * - - -def before_tag(context, tag): - """Define steps run before each tag.""" - - userdata = context.config.userdata - if tag == 'kube-prometheus-stack': - context.grafana = get_values(userdata, 'grafana') - - if tag == 'nextcloud': - context.nextcloud = get_values(userdata, 'nextcloud') - - if tag == 'rocketchat': - context.rocketchat = get_values(userdata, 'rocketchat') - - if tag == 'wordpress': - context.wordpress = get_values(userdata, 'wordpress') diff --git a/test/behave/features/kube-prometheus-stack.feature b/test/behave/features/kube-prometheus-stack.feature deleted file mode 100644 index 52cca2246f35031a5dc4437032b6aed5e5e37cdd..0000000000000000000000000000000000000000 --- a/test/behave/features/kube-prometheus-stack.feature +++ /dev/null @@ -1,26 +0,0 @@ -@kube-prometheus-stack -Feature: Test grafana admin login - As an OAS admin - I want to be able to login to grafana as the user admin - And I want to be able to look at the logs - -Scenario: Open grafana - Given I have closed all but the first window - And I open the grafana URL - Then I wait on element "//input[@name='user']" for 25000ms to be visible - And I expect that the title is "Grafana" - And I expect that element "//input[@name='password']" is visible - And I expect that the path is "/login" - -Scenario: Login to grafana - Given the element "//input[@name='user']" is visible - When I enter the "grafana" "username" in the inputfield "//input[@name='user']" - And I enter the "grafana" "password" in the inputfield "//input[@name='password']" - And I click on the button "//button[@aria-label='Login button']" - Then I wait on element "sidemenu.sidemenu" for 60000ms to be visible - And I expect that the path is "/" - -Scenario: As an admin I want to look at the helm-controller logs - When I open the grafana explore helm-controller URL - Then I wait on element ".graph-panel" for 25000ms to be visible - And I expect that element ".datapoints-warning" does not exist diff --git a/test/behave/features/nextcloud.feature b/test/behave/features/nextcloud.feature deleted file mode 100644 index eeb638db1624a598ef9dcb46e83ea9f4267d79e2..0000000000000000000000000000000000000000 --- a/test/behave/features/nextcloud.feature +++ /dev/null @@ -1,54 +0,0 @@ -@nextcloud -Feature: Test nextcloud admin login - As an OAS admin - I want to be able to login to nextcloud as the user admin - And I want to be able to open a document in OnlyOffice - -Scenario: Test OnlyOffice welcome screen - Given I have closed all but the first window - And I open the onlyoffice URL - Then I wait on element "#status-ok-icon" for 1000ms to exist - -Scenario: Open nextcloud - When I open the nextcloud URL - Then I wait on element "input#user" for 25000ms to be visible - And I expect that element "input#user" is visible - -Scenario: Login to nextcloud - Given the element "input#user" is visible - When I enter the "nextcloud" "username" in the inputfield "input#user" - And I enter the "nextcloud" "password" in the inputfield "input#password" - And I click on the button "input#submit-form" - Then I expect that element ".logo" does exist - And I expect that cookie "nc_session_id" exists - And I expect that cookie "nc_token" exists - And I expect that cookie "nc_username" exists - -Scenario: Close welcome to nextcloud modal - When I close the nextcloud welcome wizard if it exists - Then I wait on element "#firstrunwizard" for 1000ms to not exist - And I wait on element "[aria-label='Files']" for 5000ms to be visible - -Scenario: Create a new document in OnlyOffice - When I click on the element "[aria-label='Files']" - # Unfortunaty there's no check if the element that's already visible - # is also clickable. - And I wait on element "[class='button new']" to be clickable - And I click on the button "[class='button new']" - And I click on the element "[data-action='onlyofficeDocx']" - And I add a random string to the inputfield ".filenameform input[type=text]" - And I click on the element "input.icon-confirm" - And I focus the last opened tab - Then I expect a new tab has been opened - And I expect that element "div.toast-error" does not exist - -Scenario: Assert the bold button is not activated - When I wait for the iframe named "frameEditor" and switch to it - Then I wait on element "div.asc-loadmask" for 20000ms to be visible - And I wait on element "div.asc-loadmask" for 20000ms to not exist - And I wait on element "[id='id-toolbar-btn-bold']" for 20000ms to be visible - And I expect that element "[id='id-toolbar-btn-bold']" does not have the class "active" - -Scenario: Active the bold button - When I click on the element "[id='id-toolbar-btn-bold']" - Then I expect that element "[id='id-toolbar-btn-bold']" has the class "active" diff --git a/test/behave/features/rocketchat.feature b/test/behave/features/rocketchat.feature deleted file mode 100644 index 0351712775c052b814567dbd180a01b364e0d19b..0000000000000000000000000000000000000000 --- a/test/behave/features/rocketchat.feature +++ /dev/null @@ -1,22 +0,0 @@ -@rocketchat -Feature: Test rocketchat admin login - As an OAS admin - I want to be able to login to rocketchat as the user admin - -Scenario: Open rocketchat - Given I have closed all but the first window - And I open the rocketchat URL - Then I wait on element "//input[@name='emailOrUsername']" for 25000ms to be visible - And I expect that element "#pass" is visible - -Scenario: See SSO button - When I open the rocketchat URL - Then I wait on element "//input[@name='emailOrUsername']" for 25000ms to be visible - Then I wait on element "//button[@title='Openappstack']" for 5000ms to be visible - -Scenario: Login to rocketchat - Given the element "//input[@name='emailOrUsername']" is visible - When I enter the "rocketchat" "username" in the inputfield "//input[@name='emailOrUsername']" - And I enter the "rocketchat" "password" in the inputfield "#pass" - And I click on the button ".login" - Then I wait on element ".rooms-list" for 25000ms to be visible diff --git a/test/behave/features/steps/steps.py b/test/behave/features/steps/steps.py deleted file mode 100644 index b4d93bb6c4bae413093952f24d22a8565382248d..0000000000000000000000000000000000000000 --- a/test/behave/features/steps/steps.py +++ /dev/null @@ -1,38 +0,0 @@ -from oas_behave.common_steps import * -from selenium.webdriver.common.by import By -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.support.ui import WebDriverWait - -@when(u'I open the rocketchat URL') -@given(u'I open the rocketchat URL') -def step_impl(context): - """Open rocketchat URL.""" - context.behave_driver.get(context.rocketchat['url']) - -@when(u'I open the wordpress URL') -@given(u'I open the wordpress URL') -def step_impl(context): - """Open wordpress URL.""" - context.behave_driver.get(context.wordpress['url']) - -@when(u'I open the grafana explore helm-controller URL') -@given(u'I open the grafana explore helm-controller URL') -def step_impl(context): - """Open wordpress URL.""" - helm_controller_url = str(context.grafana["url"]) + '/explore?orgId=1&left=["now-1h","now","Loki",{"expr":"{app=\\\"helm-controller\\\"}"}]' - print(helm_controller_url) - context.behave_driver.get(helm_controller_url) - -@when(u'I wait on element "{element}" to be clickable') -@given(u'I wait on element "{element}" to be clickable') -def step_impl(context, element): - """Wait for element ro be clickable.""" - wait = WebDriverWait(context.behave_driver, 30) - wait.until(EC.element_to_be_clickable((By.CLASS_NAME, "button.new"))) - -@when(u'I wait for the iframe named "{name}" and switch to it') -@given(u'I wait for the iframe named "{name}" and switch to it') -def step_impl(context, name): - """Wait for the iframe with given name and switch to it.""" - wait = WebDriverWait(context.behave_driver, 30) - wait.until(EC.frame_to_be_available_and_switch_to_it((By.NAME, name))) diff --git a/test/behave/features/wordpress.feature b/test/behave/features/wordpress.feature deleted file mode 100644 index 076173a750b3c0df177e435a42c0d8de7ea69c5e..0000000000000000000000000000000000000000 --- a/test/behave/features/wordpress.feature +++ /dev/null @@ -1,17 +0,0 @@ -@wordpress -Feature: Test WordPress admin login - As an OAS admin - I want to be able to login to WordPress as the user admin - -Scenario: Open WordPress - Given I have closed all but the first window - And I open the wordpress URL - Then I wait on element "#user_login" for 25000ms to be visible - And I expect that element "#user_pass" is visible - -Scenario: Login to WordPress - Given the element "#user_login" is visible - When I enter the "wordpress" "username" in the inputfield "#user_login" - And I enter the "wordpress" "password" in the inputfield "#user_pass" - And I click on the button "#wp-submit" - Then I wait on element "#wpwrap" for 25000ms to be visible diff --git a/test/taiko/.eslintrc.yml b/test/taiko/.eslintrc.yml new file mode 100644 index 0000000000000000000000000000000000000000..16c7b9d511b0afb039336b99d76606ea21d657cc --- /dev/null +++ b/test/taiko/.eslintrc.yml @@ -0,0 +1,9 @@ +env: + browser: true + es2021: true +extends: + - standard +parserOptions: + ecmaVersion: 12 + sourceType: module +rules: {} diff --git a/test/taiko/apps.js b/test/taiko/apps.js new file mode 100644 index 0000000000000000000000000000000000000000..c4984fa18b105933563b58f6cbf073a440824b2e --- /dev/null +++ b/test/taiko/apps.js @@ -0,0 +1,132 @@ +// Tests if logging into all apps works using the admin user without SSO +// For SSO login tests see ./single-sign-on +const { openBrowser, goto, textBox, into, write, click, toRightOf, below, link, press, image, waitFor, closeBrowser, screenshot } = require('taiko'); +const assert = require('assert'); + +(async () => { + try { + const taikoTests = process.env.TAIKO_TESTS || 'all' + const domain = process.env.DOMAIN + const globalTimeout = 60000 + + // https://docs.taiko.dev/api/setconfig/ + // setConfig( { observeTime: 1000}); + setConfig( { observeTime: 0, navigationTimeout: globalTimeout }); + + console.log('Executing these tests: ' + taikoTests) + await openBrowser() + + // Nextcloud and Onlyoffice + if (taikoTests.includes('nextcloud') || taikoTests === 'all') { + const nextcloudUrl = 'https://files.' + domain + const onlyofficeUrl = 'https://office.' + domain + const nextcloudUsername = process.env.NEXTCLOUD_USERNAME || 'admin' + const nextcloudPassword = process.env.NEXTCLOUD_PASSWORD + console.log('• Nextcloud and Onlyoffice') + + await goto(onlyofficeUrl + '/welcome') + await waitFor('Document Server is running') + + await goto(nextcloudUrl) + await write(nextcloudUsername, into(textBox('Username'))) + await write(nextcloudPassword, into(textBox('Password'))) + + await click('Log in') + + // Close potential nextcloud first run wizard modal + // https://github.com/nextcloud/firstrunwizard/issues/488 + // Unfortunately, we need to sleep a while since I haven't found a + // good way that closes the modal *if* it pops up, since these + // tests should also work on subsequent logins. + await waitFor(5000) + await press('Escape') + + // Open document and type some text + await click(link(), above('Add notes')) + await click('Document') + await press('Enter') + + let italicButtonId = '#id-toolbar-btn-italic' + await waitFor($(italicButtonId), globalTimeout) + + // Activate italic button + let buttonStateBefore = await evaluate($(italicButtonId), (elem) => {return elem.getAttribute('class')}) + await assert.ok(!buttonStateBefore.includes('active')) + await click($(italicButtonId)) + let buttonStateAfter = await evaluate($(italicButtonId), (elem) => {return elem.getAttribute('class')}) + await assert.ok(buttonStateAfter.includes('active')) + + await press(['H', 'i', ' ', 'f', 'r', 'o', 'm', ' ', 't', 'a', 'i', 'k', 'o', '!', 'Enter']) + + // Deactivate italic finially + await click($(italicButtonId)) + await closeTab() + } + + // Rocketchat + if (taikoTests.includes('rocketchat') || taikoTests === 'all') { + const rocketchatUrl = 'https://chat.' + domain + const rocketchatUsername = process.env.ROCKETCHAT_USERNAME || 'admin' + const rocketchatPassword = process.env.ROCKETCHAT_PASSWORD + + console.log('• Rocketchat') + await goto(rocketchatUrl) + await write(rocketchatUsername, into(textBox('Username'))) + await write(rocketchatPassword, into(textBox('Password'))) + await click('Login') + await waitFor('Welcome to Rocket.Chat!') + } + + // Wordpress + if (taikoTests.includes('wordpress') || taikoTests === 'all') { + const wordpressUrl = 'https://www.' + domain + const wordpressUsername = process.env.WORDPRESS_USERNAME || 'admin' + const wordpressPassword = process.env.WORDPRESS_PASSWORD + + console.log('• Wordpress') + await goto(wordpressUrl) + await click('Log in') + await write(wordpressUsername, into(textBox('Username'))) + await write(wordpressPassword, into(textBox('Password'))) + await click('Log in') + await assert.ok(await link('Dashboard').exists()) + } + + // Grafana + if (taikoTests.includes('grafana') || taikoTests === 'all') { + const grafanaUrl = 'https://grafana.' + domain + const grafanaUsername = process.env.GRAFANA_USERNAME || 'admin' + const grafanaPassword = process.env.GRAFANA_PASSWORD + + console.log('• Grafana') + await goto(grafanaUrl) + await write(grafanaUsername, into(textBox('Username'))) + await write(grafanaPassword, into(textBox('Password'))) + await click('Log in') + + // Node exporter dashboard + // Couldn't select "Manage dashboards" from the sidebar menu easily, + // so we just go there + await goto(grafanaUrl + '/dashboards') + await click(link('Nodes')) + await('CPU Usage') + + // Explore Loki log messages + await goto(grafanaUrl + '/explore') + await click(image(toRightOf('Explore'))) + await click('Loki') + await click('Log browser') + await click('app') + await click('cert-manager') + await click('grafana') + await click('Show logs') + } + + } catch (error) { + await screenshot() + console.error(error) + process.exitCode = 1 + } finally { + await closeBrowser() + } +})() diff --git a/test/taiko/single-sign-on.js b/test/taiko/single-sign-on.js new file mode 100644 index 0000000000000000000000000000000000000000..8dc60bc76ef3227ad95521fccb8f3fd345f4d283 --- /dev/null +++ b/test/taiko/single-sign-on.js @@ -0,0 +1,91 @@ +// Tests if logging into all apps works using SSO +// Unfortunately we still can't run this test in CI because we haven't found +// a way to use SSO with LE staging certs. +// See https://open.greenhost.net/openappstack/single-sign-on/-/issues/62 + +const { openBrowser, goto, textBox, into, write, click, toRightOf, below, link, press, image, waitFor, closeBrowser, screenshot } = require('taiko'); +const assert = require('assert'); + +(async () => { + try { + const taikoTests = process.env.TAIKO_TESTS || 'all' + const username = process.env.SSO_USERNAME + const pw = process.env.SSO_USER_PW + const domain = process.env.DOMAIN + const adminpanelUrl = 'https://admin.' + domain + const grafanaUrl = 'https://grafana.' + domain + const globalTimeout = 60000 + + // https://docs.taiko.dev/api/setconfig/ + // setConfig( { observeTime: 1000}); + setConfig( { observeTime: 0, navigationTimeout: globalTimeout }); + + console.log('Executing these tests: ' + taikoTests) + console.log('• Login to admin panel') + await openBrowser() + + await goto(adminpanelUrl) + await click('Login') + await click('Login with OAS') + await write(username, into(textBox('Username'))) + await write(pw, into(textBox('Password'))) + await click('Remember me') + await click('Sign in') + + // Nextcloud + if (taikoTests.includes('nextcloud') || taikoTests === 'all') { + console.log('• Nextcloud and Onlyoffice') + await click(link(below('nextcloud'))) + await click('Log in with OpenAppStack') + await click('Continue with ' + username) + + // Close potential nextcloud first run wizard modal + // https://github.com/nextcloud/firstrunwizard/issues/488 + // Unfortunately, we need to sleep a while since I haven't found a + // good way that closes the modal *if* it pops up, since these + // tests should also work on subsequent logins. + await waitFor(5000) + await press('Escape') + + await assert.ok(await text('Add notes, lists or links …').exists()); + } + + // Rocketchat + if (taikoTests.includes('rocketchat') || taikoTests === 'all') { + // Navigate to rocketchat + // Breaks because of 2fa challenge which is not recieved + // by email + // https://open.greenhost.net/openappstack/openappstack/-/issues/819 + // await click(link(below('rocketchat'))); + // await click('Login via OpenAppStack'); + // await click('Continue with ' + username) + } + + // Wordpress + if (taikoTests.includes('wordpress') || taikoTests === 'all') { + console.log('• Wordpress') + await goto(adminpanelUrl) + await click(link(below('wordpress'))) + await click('Log in') + await click('Login with OpenID Connect') + await click('Continue with ' + username) + await assert.ok(await link('Dashboard').exists()) + } + + // Grafana + if (taikoTests.includes('grafana') || taikoTests === 'all') { + console.log('• Grafana') + await goto(adminpanelUrl) + await click(link(below('grafana'))) + await click('Sign in with OpenAppStack') + await click('Continue with ' + username) + await assert.ok(await text('Welcome to Grafana').exists()); + } + } catch (error) { + await screenshot() + console.error(error) + process.exitCode = 1 + } finally { + await closeBrowser() + } +})()