diff --git a/core/plugins/stack/python/mod.test.ts b/core/plugins/stack/python/mod.test.ts index fa618b5..cd588fd 100644 --- a/core/plugins/stack/python/mod.test.ts +++ b/core/plugins/stack/python/mod.test.ts @@ -163,7 +163,7 @@ Deno.test("Plugins > Check if python version and django project is identified PI name: "pipenv", commands: { install: "python -m pip install pipenv; pipenv install --dev", - run: "pipenv run", + run: "pipenv run ", }, }, type: "webApp", @@ -203,7 +203,7 @@ Deno.test("Plugins > Check if python version and a non-django-project PIPENV", a name: "pipenv", commands: { install: "python -m pip install pipenv; pipenv install --dev", - run: "pipenv run", + run: "pipenv run ", }, }, type: null, @@ -248,7 +248,7 @@ Deno.test("Plugins > Check if python version and django project is identified PO name: "poetry", commands: { install: "python -m pip install poetry; poetry install", - run: "poetry run", + run: "poetry run ", }, }, type: "webApp", @@ -288,7 +288,7 @@ Deno.test("Plugins > Check if python version and a non-django-project POETRY", a name: "poetry", commands: { install: "python -m pip install poetry; poetry install", - run: "poetry run", + run: "poetry run ", }, }, type: null, diff --git a/core/plugins/stack/python/packageManager.ts b/core/plugins/stack/python/packageManager.ts index 929a4fa..43ca466 100644 --- a/core/plugins/stack/python/packageManager.ts +++ b/core/plugins/stack/python/packageManager.ts @@ -26,7 +26,7 @@ const poetry: IntrospectFn = async (context) => { name: "poetry", commands: { install: "python -m pip install poetry; poetry install", - run: "poetry run", + run: "poetry run ", }, }; } @@ -42,7 +42,7 @@ const pipenv: IntrospectFn = async (context) => { name: "pipenv", commands: { install: "python -m pip install pipenv; pipenv install --dev", - run: "pipenv run", + run: "pipenv run ", }, }; } diff --git a/tests/default_test.ts b/tests/default_test.ts index b5676c9..c818602 100644 --- a/tests/default_test.ts +++ b/tests/default_test.ts @@ -177,3 +177,16 @@ test( await assertExpectedFiles(); }, ); + +test( + { fixture: "python/python-pipenv", args: ["--no-strict"] }, + async (stdout, _stderr, code, assertExpectedFiles) => { + assertStringIncludes(stdout, "Detected stack: python"); + assertStringIncludes( + stdout, + "No formatters for python were identified in the project, creating default pipeline with 'black' WITHOUT any specific configuration", + ); + assertEquals(code, 0); + await assertExpectedFiles(); + }, +); diff --git a/tests/fixtures/python/python-pipenv/expected/.github/workflows/pipelinit.python.format.yaml b/tests/fixtures/python/python-pipenv/expected/.github/workflows/pipelinit.python.format.yaml new file mode 100644 index 0000000..749d788 --- /dev/null +++ b/tests/fixtures/python/python-pipenv/expected/.github/workflows/pipelinit.python.format.yaml @@ -0,0 +1,22 @@ +# Generated with pipelinit 0.3.0 +# https://pipelinit.com/ +name: Format Python +on: + pull_request: + paths: + - "**.py" +jobs: + format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: "3.8" + - run: python -m pip install pipenv; pipenv install --dev + + - run: python -m pip install pip black + + - run: pipenv run black . --check + - run: pipenv run isort . -c diff --git a/tests/fixtures/python/python-pipenv/expected/.github/workflows/pipelinit.python.lint.yaml b/tests/fixtures/python/python-pipenv/expected/.github/workflows/pipelinit.python.lint.yaml new file mode 100644 index 0000000..6ffac12 --- /dev/null +++ b/tests/fixtures/python/python-pipenv/expected/.github/workflows/pipelinit.python.lint.yaml @@ -0,0 +1,23 @@ +# Generated with pipelinit 0.3.0 +# https://pipelinit.com/ +name: Lint Python +on: + pull_request: + paths: + - "**.py" +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: "3.8" + - run: python -m pip install pipenv; pipenv install --dev + + + # Adapts Flake8 to run with the Black formatter, using the '--ignore' flag to skip incompatibilities errors + # Reference: https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html?highlight=other%20tools#id1 + - run: pipenv run flake8 --ignore E203,E501,W503 . + - run: pipenv run bandit -r . diff --git a/tests/fixtures/python/python-pipenv/expected/.github/workflows/pipelinit.python.sast.yaml b/tests/fixtures/python/python-pipenv/expected/.github/workflows/pipelinit.python.sast.yaml new file mode 100644 index 0000000..e512846 --- /dev/null +++ b/tests/fixtures/python/python-pipenv/expected/.github/workflows/pipelinit.python.sast.yaml @@ -0,0 +1,17 @@ +# Generated with pipelinit 0.3.0 +# https://pipelinit.com/ +name: SAST Python +on: + pull_request: + paths: + - "**.py" +jobs: + semgrep: + name: Scan + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: returntocorp/semgrep-action@v1 + with: + config: >- + p/owasp-top-ten diff --git a/tests/fixtures/python/python-pipenv/expected/.github/workflows/pipelinit.python.test.yaml b/tests/fixtures/python/python-pipenv/expected/.github/workflows/pipelinit.python.test.yaml new file mode 100644 index 0000000..e314383 --- /dev/null +++ b/tests/fixtures/python/python-pipenv/expected/.github/workflows/pipelinit.python.test.yaml @@ -0,0 +1,21 @@ +# Generated with pipelinit 0.3.0 +# https://pipelinit.com/ +name: Test Python +on: + pull_request: + paths: + - "**.py" +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.8 + uses: actions/setup-python@v2 + with: + python-version: "3.8" + - run: python -m pip install pipenv; pipenv install --dev + + - name: Run Tests + run: | + python manage.py test diff --git a/tests/fixtures/python/python-pipenv/expected/.gitlab-ci.yml b/tests/fixtures/python/python-pipenv/expected/.gitlab-ci.yml new file mode 100644 index 0000000..4313061 --- /dev/null +++ b/tests/fixtures/python/python-pipenv/expected/.gitlab-ci.yml @@ -0,0 +1,13 @@ +# Generated with pipelinit 0.3.0 +# https://pipelinit.com/ +stages: + - lint + - format + - sast + - test + +include: + - local: ".gitlab-ci/pipelinit.python.lint.yaml" + - local: ".gitlab-ci/pipelinit.python.format.yaml" + - local: ".gitlab-ci/pipelinit.python.test.yaml" + - local: ".gitlab-ci/pipelinit.python.sast.yaml" diff --git a/tests/fixtures/python/python-pipenv/expected/.gitlab-ci/pipelinit.python.format.yaml b/tests/fixtures/python/python-pipenv/expected/.gitlab-ci/pipelinit.python.format.yaml new file mode 100644 index 0000000..8fb6a9b --- /dev/null +++ b/tests/fixtures/python/python-pipenv/expected/.gitlab-ci/pipelinit.python.format.yaml @@ -0,0 +1,16 @@ +# Generated with pipelinit 0.3.0 +# https://pipelinit.com/ +format: + stage: format + image: python:3.8 + needs: [] + script: + - python -m pip install pipenv; pipenv install --dev + - python -m pip install pip black + - pipenv run black . --check + - pipenv run isort . -c + only: + refs: + - merge_requests + changes: + - "**/*.py" diff --git a/tests/fixtures/python/python-pipenv/expected/.gitlab-ci/pipelinit.python.lint.yaml b/tests/fixtures/python/python-pipenv/expected/.gitlab-ci/pipelinit.python.lint.yaml new file mode 100644 index 0000000..c49bc84 --- /dev/null +++ b/tests/fixtures/python/python-pipenv/expected/.gitlab-ci/pipelinit.python.lint.yaml @@ -0,0 +1,16 @@ +# Generated with pipelinit 0.3.0 +# https://pipelinit.com/ +lint: + stage: lint + image: python:3.8 + needs: [] + script: + - python -m pip install pipenv; pipenv install --dev + # Adapts Flake8 to run with the Black formatter, using the '--ignore' flag to skip incompatibilities errors + # Reference: https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html?highlight=other%20tools#id1 + - pipenv run flake8 --ignore E203,E501,W503 . + only: + refs: + - merge_requests + changes: + - "**/*.py" diff --git a/tests/fixtures/python/python-pipenv/expected/.gitlab-ci/pipelinit.python.sast.yaml b/tests/fixtures/python/python-pipenv/expected/.gitlab-ci/pipelinit.python.sast.yaml new file mode 100644 index 0000000..d4eb560 --- /dev/null +++ b/tests/fixtures/python/python-pipenv/expected/.gitlab-ci/pipelinit.python.sast.yaml @@ -0,0 +1,28 @@ +# Generated with pipelinit 0.3.0 +# https://pipelinit.com/ +semgrep: + stage: sast + image: returntocorp/semgrep-agent:v1 + needs: ["format", "lint"] + only: + refs: + - merge_requests + changes: + - "**/*.py" + script: semgrep-agent + variables: + SEMGREP_RULES: >- + p/owasp-top-ten + +bandit: + stage: sast + image: python:3.8 + needs: ["format", "lint"] + script: + - python -m pip install pipenv; pipenv install --dev + - pipenv run bandit -r . + only: + refs: + - merge_requests + changes: + - "**/*.py" diff --git a/tests/fixtures/python/python-pipenv/expected/.gitlab-ci/pipelinit.python.test.yaml b/tests/fixtures/python/python-pipenv/expected/.gitlab-ci/pipelinit.python.test.yaml new file mode 100644 index 0000000..ca9fc54 --- /dev/null +++ b/tests/fixtures/python/python-pipenv/expected/.gitlab-ci/pipelinit.python.test.yaml @@ -0,0 +1,14 @@ +# Generated with pipelinit 0.3.0 +# https://pipelinit.com/ +test: + stage: test + image: python:3.8 + needs: ["format", "lint", "bandit", "semgrep"] + only: + refs: + - merge_requests + changes: + - "**/*.py" + script: + - python -m pip install pipenv; pipenv install --dev + - python manage.py test diff --git a/tests/fixtures/python/python-pipenv/project/.pipelinit.toml b/tests/fixtures/python/python-pipenv/project/.pipelinit.toml new file mode 100644 index 0000000..95a86a6 --- /dev/null +++ b/tests/fixtures/python/python-pipenv/project/.pipelinit.toml @@ -0,0 +1 @@ +platforms = ["github", "gitlab"] diff --git a/tests/fixtures/python/python-pipenv/project/Pipfile b/tests/fixtures/python/python-pipenv/project/Pipfile new file mode 100644 index 0000000..9e16580 --- /dev/null +++ b/tests/fixtures/python/python-pipenv/project/Pipfile @@ -0,0 +1,21 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +requests = "~=2.25" +gunicorn = "~=20.1" +whitenoise = "~=5.2" +django = "~=3.0" + +[dev-packages] +flake8 = "~=3.9.2" +isort = "~=5.9.3" +bandit = "~=1.7.0" + +[requires] +python_version = "3.8" + +[pipenv] +allow_prereleases = true diff --git a/tests/fixtures/python/python-pipenv/project/Pipfile.lock b/tests/fixtures/python/python-pipenv/project/Pipfile.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/python/python-pipenv/project/api/__init__.py b/tests/fixtures/python/python-pipenv/project/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/python/python-pipenv/project/api/asgi.py b/tests/fixtures/python/python-pipenv/project/api/asgi.py new file mode 100644 index 0000000..a99b2bf --- /dev/null +++ b/tests/fixtures/python/python-pipenv/project/api/asgi.py @@ -0,0 +1,14 @@ +""" +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "api.settings") + +application = get_asgi_application() diff --git a/tests/fixtures/python/python-pipenv/project/api/settings.py b/tests/fixtures/python/python-pipenv/project/api/settings.py new file mode 100644 index 0000000..7bf0620 --- /dev/null +++ b/tests/fixtures/python/python-pipenv/project/api/settings.py @@ -0,0 +1,118 @@ +""" +Django settings for api project. + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/3.2/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ + + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", +] + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + +ROOT_URLCONF = "api.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +WSGI_APPLICATION = "api.wsgi.application" + + +# Database +# https://docs.djangoproject.com/en/3.2/ref/settings/#databases + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", + } +} + + +# Password validation +# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/3.2/topics/i18n/ + +LANGUAGE_CODE = "en-us" + +TIME_ZONE = "UTC" + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/3.2/howto/static-files/ + +STATIC_URL = "/static/" + +# Default primary key field type +# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" diff --git a/tests/fixtures/python/python-pipenv/project/api/urls.py b/tests/fixtures/python/python-pipenv/project/api/urls.py new file mode 100644 index 0000000..71c1511 --- /dev/null +++ b/tests/fixtures/python/python-pipenv/project/api/urls.py @@ -0,0 +1,21 @@ +"""api URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/3.2/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path + +urlpatterns = [ + path("admin/", admin.site.urls), +] diff --git a/tests/fixtures/python/python-pipenv/project/api/wsgi.py b/tests/fixtures/python/python-pipenv/project/api/wsgi.py new file mode 100644 index 0000000..1425265 --- /dev/null +++ b/tests/fixtures/python/python-pipenv/project/api/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for api project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "api.settings") + +application = get_wsgi_application() diff --git a/tests/fixtures/python/python-pipenv/project/manage.py b/tests/fixtures/python/python-pipenv/project/manage.py new file mode 100755 index 0000000..7fbe893 --- /dev/null +++ b/tests/fixtures/python/python-pipenv/project/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "api.settings") + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/tests/helpers.ts b/tests/helpers.ts index b537298..471f7cf 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -85,19 +85,21 @@ const assertFunc = (fixture: string) => { const configContent = parseToml( await Deno.readTextFile(projectConfig), ); - let generatedDir = path( - `./fixtures/${fixture}/project/.github/workflows`, - ); - if (isConfig(configContent)) { - if (configContent.platforms?.includes("gitlab")) { - generatedDir = path( - `./fixtures/${fixture}/project/.gitlab-ci`, - ); - for await ( - const entry of walk(path(`./fixtures/${fixture}/project/`)) - ) { + + if (isConfig(configContent) && configContent.platforms) { + let generatedDir = ""; + for (const platform of configContent.platforms) { + switch (platform) { + case "github": + generatedDir = `./fixtures/${fixture}/project/.github/workflows`; + break; + case "gitlab": + generatedDir = `./fixtures/${fixture}/project/.gitlab-ci`; + generatedFiles.push(".gitlab-ci.yml"); + break; + } + for await (const entry of walk(path(generatedDir))) { if (!entry.isFile) continue; - if (entry.name !== ".gitlab-ci.yml") continue; generatedFiles.push(entry.name); } } @@ -108,10 +110,6 @@ const assertFunc = (fixture: string) => { if (!entry.isFile) continue; expectedFiles.push(entry.name); } - for await (const entry of walk(generatedDir)) { - if (!entry.isFile) continue; - generatedFiles.push(entry.name); - } // First compare if the generated files are the expected in name and number assertEquals(expectedFiles.sort(), generatedFiles.sort()); @@ -155,16 +153,25 @@ export async function cleanGitHubFiles(fixture: string) { const configContent = parseToml( await Deno.readTextFile(projectConfig), ); - if (isConfig(configContent)) { - if (configContent.platforms?.includes("gitlab")) { - await Deno.remove(path(`./fixtures/${fixture}/project/.gitlab-ci`), { - recursive: true, - }); - await Deno.remove(path(`./fixtures/${fixture}/project/.gitlab-ci.yml`)); - return; + + if (isConfig(configContent) && configContent.platforms) { + for (const platform of configContent.platforms) { + switch (platform) { + case "github": + await Deno.remove(path(`./fixtures/${fixture}/project/.github`), { + recursive: true, + }); + + break; + case "gitlab": + await Deno.remove(path(`./fixtures/${fixture}/project/.gitlab-ci`), { + recursive: true, + }); + await Deno.remove( + path(`./fixtures/${fixture}/project/.gitlab-ci.yml`), + ); + break; + } } } - await Deno.remove(path(`./fixtures/${fixture}/project/.github`), { - recursive: true, - }); }