Skip to content

Commit

Permalink
Merge pull request #411 from GitGuardian/agateau/verbose-everywhere
Browse files Browse the repository at this point in the history
Allow --verbose, --debug and --allow-self-signed everywhere
  • Loading branch information
agateau-gg authored Nov 18, 2022
2 parents f4cbc23 + a9e22b0 commit 71c3e74
Show file tree
Hide file tree
Showing 35 changed files with 340 additions and 121 deletions.
7 changes: 6 additions & 1 deletion ggshield/cmd/auth/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
from typing import Any

import click

from ggshield.cmd.common_options import add_common_options

from .login import login_cmd
from .logout import logout_cmd


@click.group(commands={"login": login_cmd, "logout": logout_cmd})
def auth_group() -> None:
@add_common_options()
def auth_group(**kwargs: Any) -> None:
"""Commands to manage authentication."""
5 changes: 4 additions & 1 deletion ggshield/cmd/auth/login.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import re
from typing import Optional, Tuple
from typing import Any, Optional, Tuple

import click

from ggshield.cmd.auth.utils import check_instance_has_enabled_flow
from ggshield.cmd.common_options import add_common_options
from ggshield.core.client import create_client
from ggshield.core.config import Config
from ggshield.core.oauth import OAuthClient
Expand Down Expand Up @@ -81,6 +82,7 @@ def validate_login_path(
default=None,
help="Number of days before the token expires. 0 means the token never expires.",
)
@add_common_options()
@click.pass_context
def login_cmd(
ctx: click.Context,
Expand All @@ -89,6 +91,7 @@ def login_cmd(
token_name: Optional[str],
lifetime: Optional[int],
sso_url: Optional[str],
**kwargs: Any,
) -> int:
"""
Authenticate with a GitGuardian workspace.
Expand Down
8 changes: 7 additions & 1 deletion ggshield/cmd/auth/logout.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from typing import Any

import click
from requests.exceptions import ConnectionError

from ggshield.cmd.common_options import add_common_options
from ggshield.core.client import create_client
from ggshield.core.config import Config
from ggshield.core.utils import dashboard_to_api_url
Expand All @@ -22,8 +25,11 @@
help="Whether the token should be revoked on logout before being removed from the configuration.",
)
@click.option("--all", "all_", is_flag=True, help="Iterate over every saved tokens.")
@add_common_options()
@click.pass_context
def logout_cmd(ctx: click.Context, instance: str, revoke: bool, all_: bool) -> int:
def logout_cmd(
ctx: click.Context, instance: str, revoke: bool, all_: bool, **kwargs: Any
) -> int:
"""
Remove authentication for a GitGuardian instance.
A successful logout results in the deletion of personal access token stored in the configuration.
Expand Down
91 changes: 91 additions & 0 deletions ggshield/cmd/common_options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""
This module defines options which should be available on all commands, such as the
-v, --verbose option.
To use it:
- Add the `@add_common_options()` decorator after all the `click.option()` calls of the
command function.
- Add a `**kwargs: Any` argument to the command function.
The `kwargs` argument is required because due to the way click works,
`add_common_options()` adds an argument for each option it defines.
"""
from typing import Any, Callable, Optional, cast

import click

from ggshield.cmd.debug_logs import setup_debug_logs
from ggshield.core.config.user_config import UserConfig


AnyFunction = Callable[..., Any]


def _get_config(ctx: click.Context) -> UserConfig:
return cast(UserConfig, ctx.obj["config"].user_config)


def _verbose_callback(
ctx: click.Context, param: click.Parameter, value: Optional[bool]
) -> Optional[bool]:
if value is not None:
_get_config(ctx).verbose = value
return value


_verbose_option = click.option(
"-v",
"--verbose",
is_flag=True,
default=None,
help="Verbose display mode.",
callback=_verbose_callback,
)


def debug_callback(
ctx: click.Context, param: click.Parameter, value: Optional[bool]
) -> Optional[bool]:
# The --debug option is marked as "is_eager" so that we can setup logs as soon as
# possible. If we don't then log commands for the creation of the Config instance
# are ignored
if value is not None:
setup_debug_logs(value is True)
return value


_debug_option = click.option(
"--debug",
is_flag=True,
default=None,
is_eager=True,
help="Show debug information.",
callback=debug_callback,
)


def allow_self_signed_callback(
ctx: click.Context, param: click.Parameter, value: Optional[bool]
) -> Optional[bool]:
if value is not None:
_get_config(ctx).allow_self_signed = value
return value


_allow_self_signed_option = click.option(
"--allow-self-signed",
is_flag=True,
default=None,
help="Ignore ssl verification.",
callback=allow_self_signed_callback,
)


def add_common_options() -> Callable[[AnyFunction], AnyFunction]:
def decorator(cmd: AnyFunction) -> AnyFunction:
_verbose_option(cmd)
_debug_option(cmd)
_allow_self_signed_option(cmd)
return cmd

return decorator
7 changes: 6 additions & 1 deletion ggshield/cmd/config/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
from typing import Any

import click

from ggshield.cmd.common_options import add_common_options

from .config_get import config_get_command
from .config_list import config_list_cmd
from .config_migrate import config_migrate_cmd
Expand All @@ -16,5 +20,6 @@
"migrate": config_migrate_cmd,
}
)
def config_group() -> None:
@add_common_options()
def config_group(**kwargs: Any) -> None:
"""Commands to manage configuration."""
6 changes: 4 additions & 2 deletions ggshield/cmd/config/config_get.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from typing import Optional
from typing import Any, Optional

import click

from ggshield.cmd.common_options import add_common_options
from ggshield.cmd.config.constants import FIELD_OPTIONS
from ggshield.core.config import Config
from ggshield.core.config.errors import UnknownInstanceError
Expand All @@ -17,9 +18,10 @@
metavar="URL",
help="Get per instance configuration.",
)
@add_common_options()
@click.pass_context
def config_get_command(
ctx: click.Context, field_name: str, instance_url: Optional[str]
ctx: click.Context, field_name: str, instance_url: Optional[str], **kwargs: Any
) -> int:
"""
Print the value of the given configuration key.
Expand Down
6 changes: 5 additions & 1 deletion ggshield/cmd/config/config_list.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
from typing import Any

import click

from ggshield.cmd.common_options import add_common_options
from ggshield.core.config import Config

from .constants import DATETIME_FORMAT


@click.command()
@click.pass_context
def config_list_cmd(ctx: click.Context) -> int:
@add_common_options()
def config_list_cmd(ctx: click.Context, **kwargs: Any) -> int:
"""
Print the list of configuration keys and values.
"""
Expand Down
6 changes: 5 additions & 1 deletion ggshield/cmd/config/config_migrate.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import os
from typing import Any

import click

from ggshield.cmd.common_options import add_common_options


@click.command()
@click.pass_context
def config_migrate_cmd(ctx: click.Context) -> None:
@add_common_options()
def config_migrate_cmd(ctx: click.Context, **kwargs: Any) -> None:
"""
Migrate configuration file to the latest version
"""
Expand Down
10 changes: 8 additions & 2 deletions ggshield/cmd/config/config_set.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from typing import Optional
from typing import Any, Optional

import click

from ggshield.cmd.common_options import add_common_options
from ggshield.core.config import Config

from .constants import FIELD_OPTIONS
Expand All @@ -17,9 +18,14 @@
metavar="URL",
help="Set per instance configuration.",
)
@add_common_options()
@click.pass_context
def config_set_command(
ctx: click.Context, field_name: str, value: str, instance: Optional[str]
ctx: click.Context,
field_name: str,
value: str,
instance: Optional[str],
**kwargs: Any,
) -> int:
"""
Update the value of the given configuration key.
Expand Down
10 changes: 8 additions & 2 deletions ggshield/cmd/config/config_unset.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from typing import Optional
from typing import Any, Optional

import click

from ggshield.cmd.common_options import add_common_options
from ggshield.core.config import Config

from .constants import FIELD_OPTIONS
Expand All @@ -18,9 +19,14 @@
help="Set per instance configuration.",
)
@click.option("--all", "all_", is_flag=True, help="Iterate over every instances.")
@add_common_options()
@click.pass_context
def config_unset_command(
ctx: click.Context, field_name: str, instance_url: Optional[str], all_: bool
ctx: click.Context,
field_name: str,
instance_url: Optional[str],
all_: bool,
**kwargs: Any,
) -> int:
"""
Remove the value of the given configuration key.
Expand Down
29 changes: 29 additions & 0 deletions ggshield/cmd/debug_logs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import logging
import sys


LOG_FORMAT = "%(asctime)s %(levelname)s %(process)x:%(thread)x %(name)s:%(funcName)s:%(lineno)d %(message)s"


def setup_debug_logs(debug: bool) -> None:
"""Configure Python logger. Disable messages up to logging.ERROR level by default.
The reason we disable error messages is that we call logging.error() in addition to
showing user-friendly error messages, but we don't want the error logs to show up
with the user-friendly error messages, unless --debug has been set.
"""
level = logging.DEBUG if debug else logging.CRITICAL

if sys.version_info[:2] < (3, 8):
# Simulate logging.basicConfig() `force` argument, introduced in Python 3.8
root = logging.getLogger()
for handler in root.handlers[:]:
root.removeHandler(handler)
handler.close()
logging.basicConfig(filename=None, level=level, format=LOG_FORMAT)
else:
logging.basicConfig(filename=None, level=level, format=LOG_FORMAT, force=True)

if debug:
# Silence charset_normalizer, its debug output does not bring much
logging.getLogger("charset_normalizer").setLevel(logging.WARNING)
6 changes: 5 additions & 1 deletion ggshield/cmd/iac/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
from typing import Any

import click

from ggshield.cmd.common_options import add_common_options
from ggshield.cmd.iac.scan import scan_cmd


@click.group(commands={"scan": scan_cmd})
def iac_group() -> None:
@add_common_options()
def iac_group(**kwargs: Any) -> None:
"""Commands to work with infrastructure as code."""
10 changes: 4 additions & 6 deletions ggshield/cmd/iac/scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import click
from pygitguardian.models import Detail

from ggshield.cmd.common_options import add_common_options
from ggshield.core.client import create_client_from_config
from ggshield.core.config import Config
from ggshield.core.constants import MAX_TAR_CONTENT_SIZE
Expand Down Expand Up @@ -72,22 +73,23 @@ def validate_exclude(_ctx: Any, _param: Any, value: Sequence[str]) -> Sequence[s
"directory",
type=click.Path(exists=True, readable=True, path_type=Path, file_okay=False),
)
@add_common_options()
@click.pass_context
def scan_cmd(
ctx: click.Context,
exit_zero: bool,
minimum_severity: str,
verbose: bool,
ignore_policies: Sequence[str],
ignore_paths: Sequence[str],
json: bool,
directory: Path,
**kwargs: Any,
) -> int:
"""
Scan a directory for IaC vulnerabilities.
"""
update_context(
ctx, exit_zero, minimum_severity, verbose, ignore_policies, ignore_paths, json
ctx, exit_zero, minimum_severity, ignore_policies, ignore_paths, json
)
result = iac_scan(ctx, directory)
scan = ScanCollection(id=str(directory), type="path_scan", iac_result=result)
Expand All @@ -100,7 +102,6 @@ def update_context(
ctx: click.Context,
exit_zero: bool,
minimum_severity: str,
verbose: bool,
ignore_policies: Sequence[str],
ignore_paths: Sequence[str],
json: bool,
Expand All @@ -118,9 +119,6 @@ def update_context(
if ignore_policies is not None:
config.user_config.iac.ignored_policies.update(ignore_policies)

if verbose is not None:
config.user_config.verbose = verbose

if exit_zero is not None:
config.user_config.exit_zero = exit_zero

Expand Down
Loading

0 comments on commit 71c3e74

Please sign in to comment.