diff --git a/docker/dev/dnsrouter-config.json b/docker/dev/dnsrouter-config.json new file mode 100644 index 00000000..a4e538d7 --- /dev/null +++ b/docker/dev/dnsrouter-config.json @@ -0,0 +1,28 @@ +{ + "log": { + "format": "nice", + "level": "debug" + }, + "servers": [ + { + "host": "0.0.0.0", + "port": 53, + "upstreams": [ + { + "regex": "website[0-9]+.example\\.com", + "upstream": "127.0.0.11" + }, + { + "regex": ".*\\.example\\.com", + "upstream": "1.1.1.1" + }, + { + "regex": "local", + "nxdomain": true + } + ], + "internal": null, + "default_upstream": "127.0.0.11" + } + ] +} diff --git a/docker/dev/letsencrypt.ini b/docker/dev/letsencrypt.ini new file mode 100644 index 00000000..93647b64 --- /dev/null +++ b/docker/dev/letsencrypt.ini @@ -0,0 +1,7 @@ +text = True +non-interactive = True +webroot-path = /data/letsencrypt-acme-challenge +key-type = ecdsa +elliptic-curve = secp384r1 +preferred-chain = ISRG Root X1 +server = diff --git a/docker/dev/pdns-db.sql b/docker/dev/pdns-db.sql new file mode 100644 index 00000000..c182cf78 --- /dev/null +++ b/docker/dev/pdns-db.sql @@ -0,0 +1,255 @@ +/* + +How this was generated: +1. bring up an empty pdns stack +2. use api to create a zone ... + +curl -X POST \ + 'http://npm.dev:8081/api/v1/servers/localhost/zones' \ + --header 'X-API-Key: npm' \ + --header 'Content-Type: application/json' \ + --data-raw '{ + "name": "example.com.", + "kind": "Native", + "masters": [], + "nameservers": [ + "ns1.pdns.", + "ns2.pdns." + ] +}' + +3. Dump sql: + +docker exec -ti npm.pdns.db mysqldump -u pdns -p pdns + +*/ + +---------------------------------------------------------------------- + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `comments` +-- + +DROP TABLE IF EXISTS `comments`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `comments` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `domain_id` int(11) NOT NULL, + `name` varchar(255) NOT NULL, + `type` varchar(10) NOT NULL, + `modified_at` int(11) NOT NULL, + `account` varchar(40) CHARACTER SET utf8mb3 DEFAULT NULL, + `comment` text CHARACTER SET utf8mb3 NOT NULL, + PRIMARY KEY (`id`), + KEY `comments_name_type_idx` (`name`,`type`), + KEY `comments_order_idx` (`domain_id`,`modified_at`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `comments` +-- + +LOCK TABLES `comments` WRITE; +/*!40000 ALTER TABLE `comments` DISABLE KEYS */; +/*!40000 ALTER TABLE `comments` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `cryptokeys` +-- + +DROP TABLE IF EXISTS `cryptokeys`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `cryptokeys` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `domain_id` int(11) NOT NULL, + `flags` int(11) NOT NULL, + `active` tinyint(1) DEFAULT NULL, + `published` tinyint(1) DEFAULT 1, + `content` text DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `domainidindex` (`domain_id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `cryptokeys` +-- + +LOCK TABLES `cryptokeys` WRITE; +/*!40000 ALTER TABLE `cryptokeys` DISABLE KEYS */; +/*!40000 ALTER TABLE `cryptokeys` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `domainmetadata` +-- + +DROP TABLE IF EXISTS `domainmetadata`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `domainmetadata` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `domain_id` int(11) NOT NULL, + `kind` varchar(32) DEFAULT NULL, + `content` text DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `domainmetadata_idx` (`domain_id`,`kind`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `domainmetadata` +-- + +LOCK TABLES `domainmetadata` WRITE; +/*!40000 ALTER TABLE `domainmetadata` DISABLE KEYS */; +INSERT INTO `domainmetadata` VALUES +(1,1,'SOA-EDIT-API','DEFAULT'); +/*!40000 ALTER TABLE `domainmetadata` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `domains` +-- + +DROP TABLE IF EXISTS `domains`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `domains` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + `master` varchar(128) DEFAULT NULL, + `last_check` int(11) DEFAULT NULL, + `type` varchar(8) NOT NULL, + `notified_serial` int(10) unsigned DEFAULT NULL, + `account` varchar(40) CHARACTER SET utf8mb3 DEFAULT NULL, + `options` varchar(64000) DEFAULT NULL, + `catalog` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `name_index` (`name`), + KEY `catalog_idx` (`catalog`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `domains` +-- + +LOCK TABLES `domains` WRITE; +/*!40000 ALTER TABLE `domains` DISABLE KEYS */; +INSERT INTO `domains` VALUES +(1,'example.com','',NULL,'NATIVE',NULL,'',NULL,NULL); +/*!40000 ALTER TABLE `domains` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `records` +-- + +DROP TABLE IF EXISTS `records`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `records` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `domain_id` int(11) DEFAULT NULL, + `name` varchar(255) DEFAULT NULL, + `type` varchar(10) DEFAULT NULL, + `content` varchar(64000) DEFAULT NULL, + `ttl` int(11) DEFAULT NULL, + `prio` int(11) DEFAULT NULL, + `disabled` tinyint(1) DEFAULT 0, + `ordername` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL, + `auth` tinyint(1) DEFAULT 1, + PRIMARY KEY (`id`), + KEY `nametype_index` (`name`,`type`), + KEY `domain_id` (`domain_id`), + KEY `ordername` (`ordername`) +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `records` +-- + +LOCK TABLES `records` WRITE; +/*!40000 ALTER TABLE `records` DISABLE KEYS */; +INSERT INTO `records` VALUES +(1,1,'example.com','NS','ns1.pdns',1500,0,0,NULL,1), +(2,1,'example.com','NS','ns2.pdns',1500,0,0,NULL,1), +(3,1,'example.com','SOA','a.misconfigured.dns.server.invalid hostmaster.example.com 2023030501 10800 3600 604800 3600',1500,0,0,NULL,1); +/*!40000 ALTER TABLE `records` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `supermasters` +-- + +DROP TABLE IF EXISTS `supermasters`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `supermasters` ( + `ip` varchar(64) NOT NULL, + `nameserver` varchar(255) NOT NULL, + `account` varchar(40) CHARACTER SET utf8mb3 NOT NULL, + PRIMARY KEY (`ip`,`nameserver`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `supermasters` +-- + +LOCK TABLES `supermasters` WRITE; +/*!40000 ALTER TABLE `supermasters` DISABLE KEYS */; +/*!40000 ALTER TABLE `supermasters` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `tsigkeys` +-- + +DROP TABLE IF EXISTS `tsigkeys`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `tsigkeys` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) DEFAULT NULL, + `algorithm` varchar(50) DEFAULT NULL, + `secret` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `namealgoindex` (`name`,`algorithm`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `tsigkeys` +-- + +LOCK TABLES `tsigkeys` WRITE; +/*!40000 ALTER TABLE `tsigkeys` DISABLE KEYS */; +/*!40000 ALTER TABLE `tsigkeys` ENABLE KEYS */; +UNLOCK TABLES; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; diff --git a/docker/dev/pebble-config.json b/docker/dev/pebble-config.json new file mode 100644 index 00000000..ea937905 --- /dev/null +++ b/docker/dev/pebble-config.json @@ -0,0 +1,12 @@ +{ + "pebble": { + "listenAddress": "0.0.0.0:443", + "managementListenAddress": "0.0.0.0:15000", + "certificate": "test/certs/localhost/cert.pem", + "privateKey": "test/certs/localhost/key.pem", + "httpPort": 80, + "tlsPort": 443, + "ocspResponderURL": "", + "externalAccountBindingRequired": false + } +} diff --git a/docker/docker-compose.ci.mysql.yml b/docker/docker-compose.ci.mysql.yml new file mode 100644 index 00000000..388cdb38 --- /dev/null +++ b/docker/docker-compose.ci.mysql.yml @@ -0,0 +1,27 @@ +# WARNING: This is a CI docker-compose file used for building and testing of the entire app, it should not be used for production. +services: + + fullstack: + environment: + DB_MYSQL_HOST: 'db-mysql' + DB_MYSQL_PORT: '3306' + DB_MYSQL_USER: 'npm' + DB_MYSQL_PASSWORD: 'npmpass' + DB_MYSQL_NAME: 'npm' + depends_on: + - db-mysql + + db-mysql: + image: jc21/mariadb-aria + environment: + MYSQL_ROOT_PASSWORD: 'npm' + MYSQL_DATABASE: 'npm' + MYSQL_USER: 'npm' + MYSQL_PASSWORD: 'npmpass' + volumes: + - mysql_vol:/var/lib/mysql + networks: + - fulltest + +volumes: + mysql_vol: diff --git a/docker/docker-compose.ci.sqlite.yml b/docker/docker-compose.ci.sqlite.yml new file mode 100644 index 00000000..1c5be48e --- /dev/null +++ b/docker/docker-compose.ci.sqlite.yml @@ -0,0 +1,9 @@ +# WARNING: This is a CI docker-compose file used for building and testing of the entire app, it should not be used for production. +services: + + fullstack: + environment: + DB_SQLITE_FILE: '/data/mydb.sqlite' + PUID: 1000 + PGID: 1000 + DISABLE_IPV6: 'true' diff --git a/scripts/ci/fulltest-cypress b/scripts/ci/fulltest-cypress new file mode 100755 index 00000000..7e4469fe --- /dev/null +++ b/scripts/ci/fulltest-cypress @@ -0,0 +1,89 @@ +#!/bin/bash +set -e + +STACK="${1:-sqlite}" + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# remember this is running in "ci" folder.. + +# Some defaults for running this script outside of CI +export COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-npm_local_fulltest}" +export IMAGE="${IMAGE:-nginx-proxy-manager}" +export BRANCH_LOWER="${BRANCH_LOWER:-unknown}" +export BUILD_NUMBER="${BUILD_NUMBER:-0000}" + +if [ "${COMPOSE_FILE:-}" = "" ]; then + export COMPOSE_FILE="docker/docker-compose.ci.yml:docker/docker-compose.ci.${STACK}.yml" +fi + +# Colors +BLUE='\E[1;34m' +RED='\E[1;31m' +CYAN='\E[1;36m' +GREEN='\E[1;32m' +RESET='\E[0m' +YELLOW='\E[1;33m' + +export BLUE CYAN GREEN RESET YELLOW + +echo -e "${BLUE}❯ ${CYAN}Starting fullstack cypress testing ...${RESET}" +echo -e "${BLUE}❯ $(docker-compose config)${RESET}" + +# $1: container_name +get_container_ip () { + local container_name=$1 + local container + local ip + container=$(docker-compose ps --all -q "${container_name}" | tail -n1) + ip=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$container") + echo "$ip" +} + +# Bring up a stack, in steps so we can inject IPs everywhere +docker-compose up -d pdns pdns-db +PDNS_IP=$(get_container_ip "pdns") +echo -e "${BLUE}❯ ${YELLOW}PDNS IP is ${PDNS_IP}${RESET}" + +# adjust the dnsrouter config +LOCAL_DNSROUTER_CONFIG="$DIR/../../docker/dev/dnsrouter-config.json" +rm -rf "$LOCAL_DNSROUTER_CONFIG.tmp" +# IMPORTANT: changes to dnsrouter-config.json will affect this line: +jq --arg a "$PDNS_IP" '.servers[0].upstreams[1].upstream = $a' "$LOCAL_DNSROUTER_CONFIG" > "$LOCAL_DNSROUTER_CONFIG.tmp" + +docker-compose up -d dnsrouter +DNSROUTER_IP=$(get_container_ip "dnsrouter") +echo -e "${BLUE}❯ ${YELLOW}DNS Router IP is ${DNSROUTER_IP}" + +if [ "${DNSROUTER_IP:-}" = "" ]; then + echo -e "${RED}❯ ERROR: DNS Router IP is not set${RESET}" + exit 1 +fi + +# mount the resolver +LOCAL_RESOLVE="$DIR/../../docker/dev/resolv.conf" +rm -rf "${LOCAL_RESOLVE}" +printf "nameserver %s\noptions ndots:0" "${DNSROUTER_IP}" > "${LOCAL_RESOLVE}" + +# bring up all remaining containers, except cypress! +docker-compose up -d --remove-orphans stepca +docker-compose pull db-mysql || true # ok to fail +docker-compose up -d --remove-orphans --pull=never fullstack + +# wait for main container to be healthy +bash "$DIR/../wait-healthy" "$(docker-compose ps --all -q fullstack)" 120 + +# Run tests +rm -rf "$DIR/../../test/results" +docker-compose up --build cypress + +# Get results +docker cp -L "$(docker-compose ps --all -q cypress):/test/results" "$DIR/../../test/" +docker cp -L "$(docker-compose ps --all -q fullstack):/data/logs" "$DIR/../../test/results/" + +if [ "$2" = "cleanup" ]; then + echo -e "${BLUE}❯ ${CYAN}Cleaning up containers ...${RESET}" + docker-compose down --remove-orphans --volumes -t 30 +fi + +echo -e "${BLUE}❯ ${GREEN}Fullstack cypress testing complete${RESET}" + diff --git a/test/cypress/config/ci.js b/test/cypress/config/ci.js new file mode 100644 index 00000000..2b50db1e --- /dev/null +++ b/test/cypress/config/ci.js @@ -0,0 +1,22 @@ +const { defineConfig } = require('cypress'); + +module.exports = defineConfig({ + requestTimeout: 30000, + defaultCommandTimeout: 20000, + reporter: 'cypress-multi-reporters', + reporterOptions: { + configFile: 'multi-reporter.json' + }, + video: true, + videosFolder: 'results/videos', + screenshotsFolder: 'results/screenshots', + e2e: { + setupNodeEvents(on, config) { + return require("../plugins/index.js")(on, config); + }, + env: { + swaggerBase: '{{baseUrl}}/api/schema', + }, + baseUrl: 'http://localhost:1234', + } +}); diff --git a/test/cypress/config/dev.js b/test/cypress/config/dev.js new file mode 100644 index 00000000..90ae943b --- /dev/null +++ b/test/cypress/config/dev.js @@ -0,0 +1,22 @@ +const { defineConfig } = require('cypress'); + +module.exports = defineConfig({ + requestTimeout: 30000, + defaultCommandTimeout: 20000, + reporter: 'cypress-multi-reporters', + reporterOptions: { + configFile: 'multi-reporter.json' + }, + video: false, + videosFolder: 'results/videos', + screenshotsFolder: 'results/screenshots', + e2e: { + setupNodeEvents(on, config) { + return require("../plugins/index.js")(on, config); + }, + env: { + swaggerBase: '{{baseUrl}}/api/schema', + }, + baseUrl: 'http://localhost:1234', + } +});