Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci-python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
with:
lint-packages: "greenbone tests"
linter: ruff check
formatter: ruff format --diff
python-version: ${{ matrix.python-version }}

test:
Expand Down
61 changes: 61 additions & 0 deletions .github/workflows/container.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: Build and Push Container Images

on:
push:
branches:
- main
tags:
- "v*"
pull_request:
branches:
- main
workflow_dispatch:
inputs:
ref-name:
type: string
description: "The ref to build a container image from. For example a tag v23.0.0."
required: true

concurrency:
group: ${{ github.workflow }}-${{ inputs.ref-name || github.ref }}
cancel-in-progress: true

jobs:
build:
if: ${{ github.repository == 'greenbone/greenbone-feed-sync' }}
name: Build and Push Container Images
uses: greenbone/workflows/.github/workflows/container-build-push-gea.yml@main
with:
ref: ${{ inputs.ref-name }}
ref-name: ${{ inputs.ref-name }}
dockerfile: .docker/Dockerfile
images: |
ghcr.io/${{ github.repository }},enable=true
${{ vars.GREENBONE_REGISTRY }}/community/${{ github.event.repository.name }},enable=${{ github.event_name != 'pull_request' }}
labels: |
org.opencontainers.image.vendor=Greenbone
org.opencontainers.image.base.name=debian/stable-slim
secrets: inherit

notify:
needs:
- build
if: ${{ !cancelled() && github.event_name != 'pull_request' && github.repository == 'greenbone/greenbone-feed-sync' }}
uses: greenbone/workflows/.github/workflows/notify-mattermost-2nd-gen.yml@main
with:
status: ${{ contains(needs.*.result, 'failure') && 'failure' || 'success' }}
secrets: inherit

trigger-replication:
needs:
- build
if: ${{ !cancelled() && github.event_name != 'pull_request' && github.repository == 'greenbone/greenbone-feed-sync' }}
runs-on: self-hosted-generic
steps:
- name: Ensure all tags are replicated on the public registry
uses: greenbone/actions/trigger-harbor-replication@v3
if: ${{ github.event_name != 'pull_request' }}
with:
registry: ${{ vars.GREENBONE_REGISTRY }}
token: ${{ secrets.GREENBONE_REGISTRY_REPLICATION_TOKEN }}
user: ${{ secrets.GREENBONE_REGISTRY_REPLICATION_USER }}
31 changes: 0 additions & 31 deletions .github/workflows/push.yml

This file was deleted.

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ Versions prior to 25.0.0 used [calendar versioning](https://calver.org/).

### Requirements

Python 3.9 and later is supported.
Python 3.10 and later is supported.

`greenbone-feed-sync` requires the `rsync` tool being installed and available
within the `PATH`.
Expand Down Expand Up @@ -619,7 +619,7 @@ first.

## License

Copyright (C) 2022-2025 [Greenbone AG][Greenbone Networks]
Copyright (C) 2022-2026 [Greenbone AG][Greenbone Networks]

Licensed under the [GNU General Public License v3.0 or later](LICENSE).

Expand Down
35 changes: 16 additions & 19 deletions greenbone/feed/sync/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,14 @@
#

import os
from collections.abc import Callable, Iterable
from dataclasses import dataclass
from pathlib import Path
from typing import (
Any,
Callable,
Generic,
Iterable,
Optional,
Protocol,
TypeVar,
Union,
)
from urllib.parse import urlsplit

Expand All @@ -31,7 +28,7 @@
import tomli as tomllib # type: ignore[no-redef]


def maybe_int(value: Optional[str]) -> Union[int, str, None]:
def maybe_int(value: str | None) -> int | str | None:
"""
Convert string into int if possible
"""
Expand Down Expand Up @@ -91,10 +88,10 @@ def resolve_gvmd_data_destination(values: ValuesDict) -> str:
class Setting(Generic[T]):
config_key: str
environment_key: str
default_value: Union[str, int, bool, None]
default_value: str | int | bool | None
value_type: ValueTypeCallable[T]

def resolve(self, values: ValuesDict) -> Optional[T]:
def resolve(self, values: ValuesDict) -> T | None:
value: Any
if self.environment_key in os.environ:
value = os.environ.get(self.environment_key)
Expand All @@ -113,7 +110,7 @@ class DependentSetting(Generic[T]):
default_value: DefaultValueCallable
value_type: ValueTypeCallable[T]

def resolve(self, values: ValuesDict) -> Optional[T]:
def resolve(self, values: ValuesDict) -> T | None:
if self.environment_key in os.environ:
value = os.environ.get(self.environment_key)
elif self.config_key in values:
Expand All @@ -126,8 +123,8 @@ def resolve(self, values: ValuesDict) -> Optional[T]:

@dataclass
class EnterpriseSettings:
user: Optional[str]
host: Optional[str]
user: str | None
host: str | None
key: Path

@classmethod
Expand Down Expand Up @@ -231,7 +228,7 @@ def feed_url(self) -> str:
DependentSetting(
"scap-data-destination",
"GREENBONE_FEED_SYNC_SCAP_DATA_DESTINATION",
lambda values: f"{values['destination-prefix']}/{DEFAULT_SCAP_DATA_PATH}", # noqa: E501
lambda values: f"{values['destination-prefix']}/{DEFAULT_SCAP_DATA_PATH}",
Path,
),
DependentSetting(
Expand All @@ -243,7 +240,7 @@ def feed_url(self) -> str:
DependentSetting(
"cert-data-destination",
"GREENBONE_FEED_SYNC_CERT_DATA_DESTINATION",
lambda values: f"{values['destination-prefix']}/{DEFAULT_CERT_DATA_PATH}", # noqa: E501
lambda values: f"{values['destination-prefix']}/{DEFAULT_CERT_DATA_PATH}",
Path,
),
DependentSetting(
Expand All @@ -261,7 +258,7 @@ def feed_url(self) -> str:
DependentSetting(
"report-formats-url",
"GREENBONE_FEED_SYNC_REPORT_FORMATS_URL",
lambda values: f"{values['feed-url']}/data-feed/{values['feed-release']}/report-formats/", # noqa: E501
lambda values: f"{values['feed-url']}/data-feed/{values['feed-release']}/report-formats/",
str,
),
DependentSetting(
Expand All @@ -273,7 +270,7 @@ def feed_url(self) -> str:
DependentSetting(
"scan-configs-url",
"GREENBONE_FEED_SYNC_SCAN_CONFIGS_URL",
lambda values: f"{values['feed-url']}/data-feed/{values['feed-release']}/scan-configs/", # noqa: E501
lambda values: f"{values['feed-url']}/data-feed/{values['feed-release']}/scan-configs/",
str,
),
DependentSetting(
Expand All @@ -285,19 +282,19 @@ def feed_url(self) -> str:
DependentSetting(
"port-lists-url",
"GREENBONE_FEED_SYNC_PORT_LISTS_URL",
lambda values: f"{values['feed-url']}/data-feed/{values['feed-release']}/port-lists/", # noqa: E501
lambda values: f"{values['feed-url']}/data-feed/{values['feed-release']}/port-lists/",
str,
),
DependentSetting(
"gvmd-lock-file",
"GREENBONE_FEED_SYNC_GVMD_LOCK_FILE",
lambda values: f"{values['destination-prefix']}/{DEFAULT_GVMD_LOCK_FILE_PATH}", # noqa: E501
lambda values: f"{values['destination-prefix']}/{DEFAULT_GVMD_LOCK_FILE_PATH}",
Path,
),
DependentSetting(
"openvas-lock-file",
"GREENBONE_FEED_SYNC_OPENVAS_LOCK_FILE",
lambda values: f"{values['destination-prefix']}/{DEFAULT_OPENVAS_LOCK_FILE_PATH}", # noqa: E501
lambda values: f"{values['destination-prefix']}/{DEFAULT_OPENVAS_LOCK_FILE_PATH}",
Path,
),
)
Expand All @@ -323,7 +320,7 @@ def load_from_config_file(self, config_file: Path) -> None:
content = config_file.read_text(encoding="utf-8")
config_data = tomllib.loads(content)
self._config = config_data.get("greenbone-feed-sync", {})
except IOError as e:
except OSError as e:
raise ConfigFileError(
f"Can't load config file {config_file.absolute()}. "
f"Error was {e}."
Expand All @@ -343,7 +340,7 @@ def apply_dependent_settings(self) -> None:
self._config[setting.config_key] = setting.resolve(self._config)

@classmethod
def load(cls, config_file: Optional[Path] = None) -> "Config":
def load(cls, config_file: Path | None = None) -> "Config":
"""
Load config values from config_file and apply all settings
"""
Expand Down
10 changes: 5 additions & 5 deletions greenbone/feed/sync/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.


from typing import Iterable, Optional
from collections.abc import Iterable


class GreenboneFeedSyncError(Exception):
Expand Down Expand Up @@ -47,8 +47,8 @@ def __init__(
returncode: int,
cmd: Iterable[str],
*,
stdout: Optional[bytes] = None,
stderr: Optional[bytes] = None,
stdout: bytes | None = None,
stderr: bytes | None = None,
) -> None:
self.returncode = returncode
self.cmd = cmd
Expand All @@ -73,9 +73,9 @@ def __init__(
self,
returncode: int,
args: Iterable[str],
stderr: Optional[bytes] = None,
stderr: bytes | None = None,
) -> None:
super().__init__(returncode, cmd=["rsync"] + list(args), stderr=stderr)
super().__init__(returncode, cmd=["rsync", *list(args)], stderr=stderr)


class FileLockingError(GreenboneFeedSyncError):
Expand Down
22 changes: 10 additions & 12 deletions greenbone/feed/sync/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
import fcntl
import os
import shutil
from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager
from pathlib import Path
from types import TracebackType
from typing import AsyncGenerator, Optional, Type, Union

from rich.console import Console
from rich.live import Live
Expand All @@ -31,10 +31,10 @@ def is_root() -> bool:

@asynccontextmanager
async def flock_wait(
path: Union[str, Path],
path: str | Path,
*,
console: Optional[Console] = None,
wait_interval: Optional[Union[int, float]] = DEFAULT_FLOCK_WAIT_INTERVAL,
console: Console | None = None,
wait_interval: int | float | None = DEFAULT_FLOCK_WAIT_INTERVAL,
) -> AsyncGenerator[None, None]:
"""
Try to lock a file and wait if it is already locked
Expand Down Expand Up @@ -128,16 +128,14 @@ def __enter__(self) -> "Spinner":

def __exit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
exc_type: type[BaseException] | None,
exc_val: BaseException | None,
exc_tb: TracebackType | None,
) -> None:
self._live.stop()


def change_user_and_group(
user: Union[str, int], group: Union[str, int]
) -> None:
def change_user_and_group(user: str | int, group: str | int) -> None:
"""
Change effective user or group of the current running process

Expand All @@ -146,7 +144,7 @@ def change_user_and_group(
group: Group name or ID
"""
if isinstance(user, str):
user_id = shutil._get_uid( # type: ignore[attr-defined] # pylint: disable=protected-access # noqa: E501
user_id = shutil._get_uid( # type: ignore[attr-defined] # pylint: disable=protected-access
user
)
if user_id is None:
Expand All @@ -155,7 +153,7 @@ def change_user_and_group(
)
user = user_id
if isinstance(group, str):
group_id = shutil._get_gid(group) # type: ignore[attr-defined] # pylint: disable=protected-access # noqa: E501
group_id = shutil._get_gid(group) # type: ignore[attr-defined] # pylint: disable=protected-access
if group_id is None:
raise GreenboneFeedSyncError(
f"Can't run as group '{group}'. Group '{group}' is unknown."
Expand Down
3 changes: 2 additions & 1 deletion greenbone/feed/sync/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
import asyncio
import subprocess
import sys
from collections.abc import Iterable
from dataclasses import dataclass
from typing import Iterable, NoReturn
from typing import NoReturn

from rich.console import Console

Expand Down
9 changes: 4 additions & 5 deletions greenbone/feed/sync/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@

import sys
from argparse import ArgumentParser, Namespace
from collections.abc import Sequence
from pathlib import Path
from typing import Any, Optional, Sequence
from typing import Any

import shtab

Expand Down Expand Up @@ -299,7 +300,7 @@ def __init__(self) -> None:

def _determine_enterprise_settings(
self, enterprise_key: Path
) -> Optional[EnterpriseSettings]:
) -> EnterpriseSettings | None:
if not enterprise_key or not enterprise_key.exists():
return None

Expand Down Expand Up @@ -332,9 +333,7 @@ def _load_config(self, config_file: str) -> Config:
def _set_defaults(self, config: ConfigDict) -> None:
self.parser.set_defaults(**_to_defaults(config))

def parse_arguments(
self, args: Optional[Sequence[str]] = None
) -> Namespace:
def parse_arguments(self, args: Sequence[str] | None = None) -> Namespace:
"""
Parse CLI arguments
"""
Expand Down
Loading
Loading