From e129485022e4ff77570918322bb2547b476e4bc8 Mon Sep 17 00:00:00 2001 From: Ricardo Branco Date: Sun, 1 Sep 2024 22:39:23 +0200 Subject: [PATCH] Remove -p option & web server support --- .github/workflows/ci.yml | 14 ---- Dockerfile | 1 - Makefile | 6 +- README.md | 17 +---- cloudview/cloudview.py | 97 +-------------------------- examples/docker-compose.yml | 23 ------- nginx/Dockerfile | 16 ----- nginx/entrypoint.sh | 51 -------------- nginx/favicon.ico | Bin 15086 -> 0 bytes nginx/site.conf.template | 59 ---------------- requirements-dev.txt | 1 - requirements-test.txt | 3 - requirements.txt | 17 +---- tests/test_selenium.py | 130 ------------------------------------ 14 files changed, 6 insertions(+), 429 deletions(-) delete mode 100644 examples/docker-compose.yml delete mode 100644 nginx/Dockerfile delete mode 100644 nginx/entrypoint.sh delete mode 100644 nginx/favicon.ico delete mode 100644 nginx/site.conf.template delete mode 100644 tests/test_selenium.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4cb9dd9..bde6a94 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,8 +49,6 @@ jobs: run: make test - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 - - name: Selenium test - run: make selenium shellcheck: name: Shellcheck @@ -61,15 +59,3 @@ jobs: uses: ludeeus/action-shellcheck@master with: scandir: . - - docker: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: docker/setup-buildx-action@v2 - - name: test docker image - run: | - docker compose -f examples/docker-compose.yml --project-directory . build --pull - PASS=testing docker compose -f examples/docker-compose.yml --project-directory . up -d - sleep 10 - test "$(curl -4k -u test:testing https://localhost:8443/test)" = "OK" diff --git a/Dockerfile b/Dockerfile index 66b6483..80fdd57 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,6 @@ RUN zypper addrepo https://download.opensuse.org/repositories/SUSE:/CA/openSUSE_ python3-cachetools \ python3-cryptography \ python3-Jinja2 \ - python3-pyramid \ python3-python-dateutil \ python3-pytz \ python3-PyYAML && \ diff --git a/Makefile b/Makefile index 051bfb9..eaf869c 100644 --- a/Makefile +++ b/Makefile @@ -18,11 +18,7 @@ pylint: .PHONY: test test: - @SKIP_SELENIUM=1 TZ=Europe/Berlin pytest --capture=sys -v --cov --cov-report term-missing - -.PHONY: selenium -selenium: - @pytest tests/test_selenium.py + TZ=Europe/Berlin pytest --capture=sys -v --cov --cov-report term-missing .PHONY: mypy mypy: diff --git a/README.md b/README.md index e0403a4..46f84ea 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Docker image available at `ghcr.io/ricardobranco777/cloudview:latest` ## Usage ``` -usage: cloudview.py [-h] [-c CONFIG] [-f FORMAT] [-l {none,debug,info,warning,error,critical}] [-o {text,html,json}] [-p PORT] [-P {ec2,gce,azure_arm,openstack}] [-r] +usage: cloudview.py [-h] [-c CONFIG] [-f FORMAT] [-l {none,debug,info,warning,error,critical}] [-o {text,html,json}] [-P {ec2,gce,azure_arm,openstack}] [-r] [-s {name,state,time}] [-S {error,migrating,normal,paused,pending,rebooting,reconfiguring,running,starting,stopped,stopping,suspended,terminated,unknown,updating}] [-t TIME_FORMAT] [-v] [--version] @@ -27,7 +27,6 @@ options: logging level (default: error) -o {text,html,json}, --output {text,html,json} output type (default: text) - -p PORT, --port PORT run a web server on specified port (default: None) -P {ec2,gce,azure_arm,openstack}, --providers {ec2,gce,azure_arm,openstack} list only specified providers (default: None) -r, --reverse reverse sort (default: False) @@ -59,16 +58,6 @@ NOTES: The [cloudview](scripts/cloudview) script scans `clouds.yaml` and environment variables to execute the proper `docker` command. -## To run the web server with Docker Compose: - -If you have a TLS key pair, put the certificates in `cert.pem`, the private key in `key.pem` and the file containing the passphrase to the private key in `key.txt`. Then edit the [docker-compose.yml](examples/docker-compose.yml) file to mount the directory to `/etc/nginx/ssl` in the container like this: `- "/path/to/tls:/etc/nginx/ssl:ro"`. Set and export the `NGINX_HOST` environment variable with the FQDN of your host. - -For HTTP Basic Authentication, create a file named `auth.htpasswd` in the same directory with the TLS key pair. - -If you don't have a TLS key pair, a self-signed certificate and a random password for logging in will be generated. You can see the latter with `docker compose logs`. The user is `test`. - -After running `docker compose build` & `docker compose up -d` you can browse to [https://localhost:8443](https://localhost:8443) - ## Debugging - For debugging you can set the `LIBCLOUD_DEBUG` environment variable to a path like `/dev/stderr` @@ -79,7 +68,3 @@ After running `docker compose build` & `docker compose up -d` you can browse to - [Azure](https://libcloud.readthedocs.io/en/stable/compute/drivers/azure_arm.html) - [GCE](https://libcloud.readthedocs.io/en/stable/compute/drivers/gce.html) - [Openstack](https://libcloud.readthedocs.io/en/stable/compute/drivers/openstack.html) - -## Similar projects - - - [public cloud watch](https://github.com/SUSE/pcw/) diff --git a/cloudview/cloudview.py b/cloudview/cloudview.py index 1ff39b8..c60b26f 100644 --- a/cloudview/cloudview.py +++ b/cloudview/cloudview.py @@ -8,18 +8,10 @@ import logging import sys from concurrent.futures import ThreadPoolExecutor -from json import JSONEncoder -from io import StringIO from operator import itemgetter from typing import Any from urllib.parse import urlencode, quote, unquote -from wsgiref.simple_server import make_server -from pyramid.view import view_config -from pyramid.config import Configurator -from pyramid.response import Response -from pyramid.request import Request - import yaml from libcloud.compute.types import Provider, LibcloudError @@ -102,49 +94,16 @@ def print_instances(client: CSP) -> None: Output().info(instance) -def print_info() -> Response | None: +def print_info() -> None: """ Print information about instances """ clients = get_clients(config_file=args.config) - sys.stdout = StringIO() if args.port else sys.stdout Output().header() if len(clients) > 0: with ThreadPoolExecutor(max_workers=len(clients)) as executor: executor.map(print_instances, clients) Output().footer() - if args.port: - response = sys.stdout.getvalue() # type: ignore - sys.stdout.close() - return response - return None - - -def handle_requests(request: Request) -> Response | None: - """ - Handle HTTP requests - """ - logging.info(request) - response = print_info() - return Response(response) - - -def test(request: Request | None = None) -> Response | None: - """ - Used for testing - """ - if request: - logging.info(request) - response = "OK" - return Response(response) - return None - - -def not_found() -> Response: - """ - Not found! - """ - return Response("Not found!", status=404) def valid_elem(elem: str) -> bool: @@ -159,50 +118,6 @@ def valid_elem(elem: str) -> bool: ) -@view_config(route_name="instance") -def handle_instance(request: Request) -> Response: - """ - Handle HTTP requests for instances - """ - logging.info(request) - provider = request.matchdict["provider"] - cloud = request.matchdict["cloud"] - instance_id = request.matchdict["instance_id"] - if ( - provider not in PROVIDERS - or not valid_elem(cloud) - or not valid_elem(instance_id) - ): - return not_found() - client = list(get_clients(config_file=args.config, provider=provider, cloud=cloud))[ - 0 - ] - if client is None: - return not_found() - if client is not None: - info = client.get_instance(instance_id, **request.params) - if info is None: - return not_found() - response = JSONEncoder(default=str, indent=4, sort_keys=True).encode(info.extra) - return Response(response, content_type="application/json; charset=utf-8") - - -def web_server() -> None: - """ - Setup the WSGI server - """ - with Configurator() as config: - config.add_route("handle_requests", "/") - config.add_view(handle_requests, route_name="handle_requests") - config.add_route("test", "/test") - config.add_view(test, route_name="test") - config.add_route("instance", "instance/{provider}/{cloud}/{instance_id}") - config.scan() - app = config.make_wsgi_app() - server = make_server("0.0.0.0", args.port, app) - server.serve_forever() - - def parse_args() -> argparse.Namespace: """ Parse command line options @@ -233,9 +148,6 @@ def parse_args() -> argparse.Namespace: choices=["text", "html", "json"], help="output type", ) - argparser.add_argument( - "-p", "--port", type=port_number, help="run a web server on specified port" - ) argparser.add_argument( "-P", "--providers", @@ -309,14 +221,7 @@ def main() -> None: if args.verbose: keys |= {"id": ""} - if args.port: - args.output = "html" Output(type=args.output.lower(), keys=keys, refresh_seconds=600) - - if args.port: - web_server() - sys.exit(1) - print_info() diff --git a/examples/docker-compose.yml b/examples/docker-compose.yml deleted file mode 100644 index 970a283..0000000 --- a/examples/docker-compose.yml +++ /dev/null @@ -1,23 +0,0 @@ -version: '3.8' - -services: - app: - build: . - command: --config /clouds.yaml --sort time --port 7777 --verbose - environment: - - TZ=Europe/Berlin - volumes: - - ${PWD}/examples/clouds.yaml:/clouds.yaml:ro - # Must be the same path mentioned in clouds.yaml - - ${PWD}/gce.json:/gce.json:ro - restart: always - nginx: - build: nginx - environment: - - APP_PORT=7777 - - NGINX_HOST=${NGINX_HOST:-localhost} - - PASS=${PASS:-} - ports: - - "8443:443" - read_only: true - restart: always diff --git a/nginx/Dockerfile b/nginx/Dockerfile deleted file mode 100644 index b58f1ea..0000000 --- a/nginx/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM nginx:alpine - -RUN apk --no-cache add bash openssl - -COPY site.conf.template /etc/nginx/conf.d/ -COPY entrypoint.sh /run -RUN chmod +x /run/entrypoint.sh && \ - ln -sf /run/site.conf /etc/nginx/conf.d/default.conf - -COPY favicon.ico /usr/share/nginx/html/ - -EXPOSE 443 - -VOLUME ["/var/cache/nginx", "/run", "/etc/nginx/ssl"] - -ENTRYPOINT ["/run/entrypoint.sh"] diff --git a/nginx/entrypoint.sh b/nginx/entrypoint.sh deleted file mode 100644 index f2a852b..0000000 --- a/nginx/entrypoint.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash - -set -e -umask 077 - -# shellcheck disable=SC2016 -envsubst '$APP_PORT $NGINX_HOST' < /etc/nginx/conf.d/site.conf.template > /run/site.conf - -openssl_config() { - cat /etc/ssl/openssl.cnf - cat <<- EOF - [custom] - basicConstraints = critical,CA:false - nsCertType = server - subjectKeyIdentifier = hash - authorityKeyIdentifier = keyid,issuer:always - keyUsage = critical,digitalSignature,keyEncipherment - extendedKeyUsage = serverAuth - subjectAltName = DNS:${NGINX_HOST:-localhost} - EOF -} - -# On read-only containers, openssl won't be able to write to ~/.rnd -export RANDFILE=/dev/null - -SSL=/etc/nginx/ssl -if [ ! -f $SSL/key.pem ] ; then - # Generate a random Base64 password - openssl rand -base64 48 > $SSL/key.txt - # Generate a self-signed certificate - openssl req -x509 -sha512 -newkey rsa:4096 -keyout $SSL/key.pem -out $SSL/cert.pem -days 365 -subj "/CN=${NGINX_HOST:-localhost}" -passout file:$SSL/key.txt -extensions custom -config <(openssl_config) - - # Generate a random salt of 16 characters - SALT=$(openssl rand -base64 12) - # The salt character set is [a-zA-Z0-9./] - SALT=${SALT//+/.} - if [ -z "$PASS" ] ; then - # Generate a random password - PASS=$(openssl rand -base64 48) - echo "Password for HTTP Basic Authentication is $PASS" - fi - # Hash password with salted SHA-512 - PASS=$(mkpasswd -S "$SALT" "$PASS") - echo "test:$PASS" >> $SSL/auth.htpasswd - unset PASS - chmod 644 $SSL/auth.htpasswd -elif [ ! -f $SSL/auth.htpasswd ] ; then - sed -i '/auth_basic/d' /run/site.conf -fi - -exec nginx -g 'daemon off;' diff --git a/nginx/favicon.ico b/nginx/favicon.ico deleted file mode 100644 index 0ed3145a9475758db083a02e001d552bf85812dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 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 M*Wvn+Dv9L(06szj^Z)<= diff --git a/nginx/site.conf.template b/nginx/site.conf.template deleted file mode 100644 index 026c04a..0000000 --- a/nginx/site.conf.template +++ /dev/null @@ -1,59 +0,0 @@ -# Hide Nginx version -server_tokens off; - -server { - listen 80; - server_name ${NGINX_HOST}; - return 301 https://$server_name$request_uri; -} - -server { - listen 443 ssl; - server_name ${NGINX_HOST}; - - ssl_certificate_key /etc/nginx/ssl/key.pem; - ssl_password_file /etc/nginx/ssl/key.txt; - ssl_certificate /etc/nginx/ssl/cert.pem; - ssl_ciphers TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256; - ssl_protocols TLSv1.3 TLSv1.2; - ssl_prefer_server_ciphers on; - ssl_session_cache shared:SSL:20m; - ssl_session_timeout 1d; - ssl_session_tickets off; - #ssl_stapling on; - #ssl_stapling_verify on; - - proxy_connect_timeout 300; - proxy_send_timeout 300; - proxy_read_timeout 300; - send_timeout 300; - - location /favicon.ico { - root /usr/share/nginx/html; - } - - location / { - limit_except GET { - deny all; - } - auth_basic on; - auth_basic_user_file /etc/nginx/ssl/auth.htpasswd; - proxy_pass http://app:${APP_PORT}; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - # Remove authentication header - proxy_set_header Authorization ""; - # Protect against HTTPoxy - proxy_set_header Proxy ""; - # Protect against gzip attacks - proxy_set_header Accept-Encoding ""; - #add_header Strict-Transport-Security max-age=15768000; - # Enable keepalive - proxy_http_version 1.1; - proxy_set_header Connection ""; - proxy_redirect off; - proxy_buffering off; - } -} diff --git a/requirements-dev.txt b/requirements-dev.txt index e6151bc..24098d4 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,7 +2,6 @@ apache-libcloud cachetools cryptography Jinja2 -pyramid python-dateutil pytz PyYAML diff --git a/requirements-test.txt b/requirements-test.txt index f355a82..0c286a6 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -13,6 +13,3 @@ types-pytz types-PyYAML types-cachetools types-requests -podman -docker -selenium diff --git a/requirements.txt b/requirements.txt index 472f9cc..10fed3d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,27 +1,16 @@ apache-libcloud==3.8.0 -cachetools==5.4.0 -certifi==2024.7.4 +cachetools==5.5.0 +certifi==2024.8.30 cffi==1.17.0 charset-normalizer==3.3.2 cryptography==43.0.0 -hupper==1.12.1 -idna==3.7 +idna==3.8 Jinja2==3.1.4 MarkupSafe==2.1.5 -PasteDeploy==3.1.0 -plaster==1.1.2 -plaster-pastedeploy==1.0.1 pycparser==2.22 -pyramid==2.0.2 python-dateutil==2.9.0.post0 pytz==2024.1 PyYAML==6.0.2 requests==2.32.3 -setuptools==72.2.0 six==1.16.0 -translationstring==1.4 urllib3==2.2.2 -venusian==3.1.0 -WebOb==1.8.8 -zope.deprecation==5.0 -zope.interface==7.0.1 diff --git a/tests/test_selenium.py b/tests/test_selenium.py deleted file mode 100644 index 998ec48..0000000 --- a/tests/test_selenium.py +++ /dev/null @@ -1,130 +0,0 @@ -# pylint: disable=missing-module-docstring,missing-function-docstring,missing-class-docstring,redefined-outer-name,no-member,unused-argument - -import contextlib -import logging -import random -import os -import shlex -import shutil -import sys -from subprocess import DEVNULL - -import pytest -import docker -from docker.errors import DockerException -from requests.exceptions import RequestException -from podman import PodmanClient -from podman.errors import APIError, PodmanError -from selenium.webdriver import firefox -from selenium.common.exceptions import WebDriverException - - -@pytest.fixture(scope="session") -def client(): - if os.getenv("SKIP_SELENIUM"): - pytest.skip("Skipping because SKIP_SELENIUM is set") - - if not shutil.which("geckodriver"): - pytest.skip("Please install geckodriver in your PATH. Skipping...") - - try: - client = docker.from_env() - except (DockerException, RequestException) as exc: - logging.warning("%s", exc) - try: - client = PodmanClient() - except (APIError, PodmanError) as exc: - pytest.skip(f"Broken Podman environment: {exc}") - if not client.info()["host"]["remoteSocket"]["exists"]: - pytest.skip("Please run systemctl --user enable --now podman.socket") - - yield client - - client.close() - - -@pytest.fixture(scope="session") -def random_port(): - # Get random number for ephemeral port, container and image name - # Typical values from /proc/sys/net/ipv4/ip_local_port_range - return random.randint(32768, 60999) - - -@pytest.fixture(scope="session") -def image(random_port, client): - image_name = f"cloudview-test{random_port}" - - # Build image - try: - client.images.build( - path=".", - dockerfile="Dockerfile", - tag=image_name, - ) - except APIError as exc: - pytest.skip(f"Broken Podman environment: {exc}") - except RequestException as exc: - pytest.skip(f"Broken Docker environment: {exc}") - except (DockerException, PodmanError) as exc: - pytest.fail(f"{exc}") - - yield image_name - - # Cleanup - with contextlib.suppress(APIError, PodmanError, DockerException, RequestException): - client.images.remove(image_name) - - -@pytest.fixture(scope="session") -def container(random_port, image, client): - command = shlex.split(f"--port {7777}") - environment = {} - if os.getenv("DEBUG"): - command.extend(shlex.split("--log debug")) - environment.update({"LIBCLOUD_DEBUG": 1}) - try: - # Run container - container = client.containers.run( - image=image, - name=image, - detach=True, - command=command, - environment=environment, - ports={f"{7777}/tcp": random_port}, - ) - except (APIError, PodmanError, DockerException, RequestException) as exc: - pytest.fail(f"{exc}") - - yield container - - # Cleanup - with contextlib.suppress(APIError, PodmanError, DockerException, RequestException): - print(container.logs(), file=sys.stderr) - with contextlib.suppress(APIError, PodmanError, DockerException, RequestException): - container.stop() - with contextlib.suppress(APIError, PodmanError, DockerException, RequestException): - container.remove() - - -@pytest.fixture -def browser(container): - service = firefox.service.Service( - log_output=sys.stderr if os.getenv("DEBUG") else DEVNULL - ) - options = firefox.options.Options() - options.add_argument("--headless") - try: - driver = firefox.webdriver.WebDriver(options=options, service=service) - except WebDriverException as exc: - pytest.fail(f"{exc}") - driver.set_page_load_timeout(30) - yield driver - driver.quit() - - -def test_web(random_port, browser): - try: - browser.get(f"http://127.0.0.1:{random_port}") - except WebDriverException as exc: - pytest.fail(f"{exc}") - assert "Instances" in browser.title