diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index abf75d7..3c6479c 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -11,16 +11,11 @@ jobs: runs-on: ubuntu-latest steps: - - name: Pull package data - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: "3.12" - - - name: Setup up uv - run: curl -LsSf https://astral.sh/uv/0.4.5/install.sh | sh + - uses: astral-sh/setup-uv@v5 - name: Install dependencies run: uv sync --all-extras --dev @@ -41,8 +36,8 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.8" - - uses: yezz123/setup-uv@v4 + python-version: "3.12" + - uses: astral-sh/setup-uv@v5 - uses: snyk/actions/setup@master - name: Export a requirements file for Snyk run: | diff --git a/tenint/__init__.py b/tenint/__init__.py index 213486e..da324a5 100644 --- a/tenint/__init__.py +++ b/tenint/__init__.py @@ -2,4 +2,4 @@ from .models.configuration import Configuration, Settings # noqa F401 from .connector import Connector # noqa F401 -__version__ = '0.1.6' +__version__ = '0.1.7' diff --git a/tenint/cli.py b/tenint/cli.py index 64f4af2..5034835 100644 --- a/tenint/cli.py +++ b/tenint/cli.py @@ -18,7 +18,9 @@ @app.command('init') def init_connector( - path: Annotated[Path, Option(help='initialization path')] = Path('.'), + path: Annotated[ + Path, Option(help='initialization path', dir_okay=True, file_okay=False) + ] = Path('.'), ): is_dirty = False for fn in ('pyproject.toml', 'connector.py'): @@ -35,16 +37,16 @@ def init_connector( with src.open('rb') as sobj, dest.open('wb') as dobj: dobj.write(sobj.read()) - tests = dest.joinpath('tests') + tests = path.joinpath('tests') if not tests.exists(): tests.mkdir() - src = tests.joinpath('test_connector.py') - dest = tmpl.joinpath('test_connector.py') + src = tmpl.joinpath('test_connector.py') + dest = tests.joinpath('test_connector.py') with dest.open('wb') as dobj, src.open('rb') as sobj: dobj.write(sobj.read()) else: console.print( - '[yellow bold]NOTE[/yellod bold]: skipped adding tests. tests folder exists.' + '[yellow bold]NOTE[/yellow bold]: skipped adding tests. tests folder exists.' ) console.print( diff --git a/tenint/connector.py b/tenint/connector.py index 489d055..adb1950 100644 --- a/tenint/connector.py +++ b/tenint/connector.py @@ -200,6 +200,15 @@ def cmd_run( case_sensitive=False, ), ] = LogLevel.info, + since: Annotated[ + int | None, + Option( + '--since', + '-s', + envvar='SINCE', + help='When did the last successful run start?', + ), + ] = None, ): """ Invoke the connector @@ -214,7 +223,7 @@ def cmd_run( try: config = self.fetch_config(json_data, filename) - self.main(config=config) + self.main(config=config, since=since) except (ValidationError, ConfigurationError) as exc: self.console.print(exc) status_code = 2 diff --git a/tenint/models/configuration.py b/tenint/models/configuration.py index 3d53811..4d7c03c 100644 --- a/tenint/models/configuration.py +++ b/tenint/models/configuration.py @@ -1,6 +1,6 @@ -from typing import Any, Self +from typing import Any -from pydantic import BaseModel, ConfigDict, Field, computed_field, model_validator +from pydantic import BaseModel, ConfigDict, Field, computed_field from .credentials import Credential diff --git a/tenint/models/credentials.py b/tenint/models/credentials.py index 6633f55..a5fc750 100644 --- a/tenint/models/credentials.py +++ b/tenint/models/credentials.py @@ -1,6 +1,6 @@ from typing import Any, Literal -from pydantic import AnyHttpUrl, BaseModel, ConfigDict, computed_field +from pydantic import AnyHttpUrl, BaseModel, ConfigDict, SecretStr, computed_field def credential_schema(schema: dict[str, Any]) -> None: @@ -41,14 +41,10 @@ class TenableVMCredential(Credential): 'Tenable Vulnerability Management' ) slug: Literal['tvm'] = 'tvm' -<<<<<<< HEAD description: str = 'Tenable Vulnerability Management Credential' -======= - definition: str = 'Tenable Vulnerability Management Credential' ->>>>>>> fe11665 (Updated Credentials to include description) url: AnyHttpUrl = 'https://cloud.tenable.com' access_key: str - secret_key: str + secret_key: SecretStr class TenableSCCredential(Credential): @@ -59,11 +55,7 @@ class TenableSCCredential(Credential): prefix: Literal['tio'] = 'tsc' name: Literal['Tenable Security Center'] = 'Tenable Security Center' slug: Literal['tvm'] = 'tsc' -<<<<<<< HEAD description: str = 'Tenable Security Center Credential' -======= - definition: str = 'Tenable Security Center Credential' ->>>>>>> fe11665 (Updated Credentials to include description) url: AnyHttpUrl access_key: str - secret_key: str + secret_key: SecretStr diff --git a/tenint/models/marketplace.py b/tenint/models/marketplace.py index b25b48f..442fbbe 100644 --- a/tenint/models/marketplace.py +++ b/tenint/models/marketplace.py @@ -3,7 +3,7 @@ import tomllib from pathlib import Path -from pydantic import AnyHttpUrl, BaseModel, EmailStr, Field +from pydantic import AnyHttpUrl, BaseModel, EmailStr from .pyproject import PyProject @@ -14,6 +14,7 @@ class MarketplaceConnector(BaseModel): description: str icon_url: AnyHttpUrl image_url: str + timeout: int marketplace_tag: str connector_owner: str support_contact: EmailStr @@ -54,6 +55,7 @@ def load_from_pyproject( description=obj.project.description, icon_url=icon_url, image_url=image_url, + timeout=obj.tool.tenint.connector.timeout, marketplace_tag=obj.project.version, connector_owner=obj.project.authors[0].name, support_contact=obj.project.authors[0].email, diff --git a/tenint/templates/connector.py b/tenint/templates/connector.py index 8eed359..5032de4 100755 --- a/tenint/templates/connector.py +++ b/tenint/templates/connector.py @@ -22,7 +22,7 @@ class AppSettings(Settings): @connector.job -def main(config: AppSettings): +def main(config: AppSettings, since: int | None = None): log.debug('This is a debug test') log.info('this is an info test') log.warning('this is a warning') diff --git a/tenint/templates/test_connector.py b/tenint/templates/test_connector.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/models/test_marketplace.py b/tests/models/test_marketplace.py new file mode 100644 index 0000000..98e8a19 --- /dev/null +++ b/tests/models/test_marketplace.py @@ -0,0 +1,41 @@ +from tenint.models.marketplace import MarketplaceConnector + + +def test_load_from_pyproject(tmpdir): + pf = tmpdir.join('pyproject-pass.toml') + pf.write(""" + [project] + name = "example-connector" + version = "0.0.1" + description = "Example Description" + dependencies = [] + + [project.optional-dependencies] + testing = [] + + [project.urls] + support = "https://example.com/support" + + [[project.authors]] + name = "Company" + email = "support@company.com" + + [tool.tenint.connector] + title = "Example Connector" + tags = ["tvm", "example"] + """) + mp = MarketplaceConnector.load_from_pyproject( + pf, + icon_url='https://somewhere.com/img.png', + image_url='tenable/connector-example', + ) + assert mp.name == 'Example Connector' + assert mp.slug == 'example-connector' + assert mp.description == 'Example Description' + assert str(mp.icon_url) == 'https://somewhere.com/img.png' + assert mp.image_url == 'tenable/connector-example' + assert mp.timeout == 3600 + assert mp.marketplace_tag == '0.0.1' + assert mp.connector_owner == 'Company' + assert mp.support_contact == 'support@company.com' + assert mp.tags == ['tvm', 'example'] diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..0939323 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,32 @@ +from typer.testing import CliRunner + +from tenint.cli import app + +runner = CliRunner() + + +def test_init_command(tmp_path): + result = runner.invoke(app, ['init', '--path', str(tmp_path)]) + if result.exception: + raise result.exception + assert result.exit_code == 0 + assert 'Now that you have' in result.stdout + assert 'skipped' not in result.stdout + + +def test_init_skip_single_file(tmp_path): + pyfile = tmp_path.joinpath('pyproject.toml') + with pyfile.open('w') as fobj: + fobj.write('something') + result = runner.invoke(app, ['init', '--path', str(tmp_path)]) + assert result.exit_code == 0 + assert 'Now that you have' in result.stdout + assert 'pyproject.toml as it already exists' in result.stdout + + +def test_init_skip_tests(tmp_path): + tmp_path.joinpath('tests').mkdir() + result = runner.invoke(app, ['init', '--path', str(tmp_path)]) + assert result.exit_code == 0 + assert 'Now that you have' in result.stdout + assert 'skipped adding tests' in result.stdout diff --git a/tests/test_connector.py b/tests/test_connector.py index 9960d89..548a75c 100644 --- a/tests/test_connector.py +++ b/tests/test_connector.py @@ -24,7 +24,7 @@ class AppSettings(Settings): @pytest.fixture def main(): - def main(config: dict[str, Any]): + def main(config: dict[str, Any], since: int | None = None): log = logging.getLogger('main-test') log.debug('This is a debug test') log.info('This is an info test') @@ -119,7 +119,7 @@ def test_connector_run_fail(AppSettings): connector = Connector(settings=AppSettings) @connector.job - def failmain(config: dict): + def failmain(config: dict, since: None = None): raise Exception('I have failed') result = runner.invoke(connector.app, ['run', '-j', '{"is_bool": true}']) diff --git a/uv.lock b/uv.lock index 764e767..1524de6 100644 --- a/uv.lock +++ b/uv.lock @@ -552,7 +552,7 @@ wheels = [ [[package]] name = "tenint" -version = "0.1.0" +version = "0.1.6" source = { editable = "." } dependencies = [ { name = "email-validator" }, @@ -609,7 +609,6 @@ dev = [ { name = "ruff", specifier = ">=0.7.1" }, { name = "tenint", editable = "." }, ] -testing = [] [[package]] name = "tomlkit"