Skip to content
Open
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
49 changes: 49 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: MythX CLI

on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, pypy3]

steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install Python dependencies
uses: py-actions/py-dependency-install@v2
with:
path: "requirements_dev.txt"
- name: Setup tox for GH actions
run: pip install tox-gh-actions
- name: Test with tox
run: make test
- name: Upload to Coveralls
run: coveralls
env:
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}

deploy:
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: "3.7"
- name: Install Python dependencies
uses: py-actions/py-dependency-install@v2
with:
path: "requirements_dev.txt"
- name: Build and publish
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: make release
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ lint: ## check style with flake8
flake8 mythx_cli tests

test: ## run tests quickly with the default Python
py.test -vv
pytest --cov-report html --cov-report term --cov mythx_cli tests/

test-all: ## run tests on every Python version with tox
tox
Expand Down
2 changes: 1 addition & 1 deletion mythx_cli/analysis/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,5 @@ def analysis_list(ctx, number: int) -> None:

# trim result to desired result number
LOGGER.debug(f"Got {len(result.analyses)} analyses, trimming to {number}")
result = AnalysisListResponse(analyses=result[:number], total=resp.total)
result = AnalysisListResponse(analyses=result.analyses[:number], total=resp.total)
write_or_print(FORMAT_RESOLVER[ctx["fmt"]].format_analysis_list(result))
7 changes: 4 additions & 3 deletions mythx_cli/analysis/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import click
from mythx_models.response import AnalysisInputResponse, DetectedIssuesResponse
from pythx import Client

from mythx_cli.formatter import FORMAT_RESOLVER, util
from mythx_cli.formatter.base import BaseFormatter
Expand Down Expand Up @@ -54,9 +55,10 @@ def analysis_report(
"""

issues_list: List[
Tuple[DetectedIssuesResponse, Optional[AnalysisInputResponse]]
Tuple[str, DetectedIssuesResponse, Optional[AnalysisInputResponse]]
] = []
formatter: BaseFormatter = FORMAT_RESOLVER[ctx["fmt"]]
ctx["client"]: Client
for uuid in uuids:
LOGGER.debug(f"{uuid}: Fetching report")
resp = ctx["client"].report(uuid)
Expand All @@ -74,8 +76,7 @@ def analysis_report(
swc_blacklist=swc_blacklist,
swc_whitelist=swc_whitelist,
)
resp.uuid = uuid
issues_list.append((resp, inp))
issues_list.append((uuid, resp, inp))

LOGGER.debug(
f"{uuid}: Printing report for {len(issues_list)} issue items with sort key \"{ctx['table_sort_key']}\""
Expand Down
2 changes: 1 addition & 1 deletion mythx_cli/analysis/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ def analysis_status(ctx, uuids: List[str]) -> None:
"""
for uuid in uuids:
LOGGER.debug(f"{uuid}: Fetching status")
resp = ctx["client"].status(uuid)
resp = ctx["client"].analysis_status(uuid)
LOGGER.debug(f"{uuid}: Printing status information")
write_or_print(FORMAT_RESOLVER[ctx["fmt"]].format_analysis_status(resp))
10 changes: 5 additions & 5 deletions mythx_cli/analyze/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,8 @@ def analyze(

if create_group:
resp: GroupCreationResponse = ctx["client"].create_group(group_name=group_name)
group_id = resp.group.identifier
group_name = resp.group.name or ""
group_id = resp.identifier
group_name = resp.name or ""

if group_id:
# associate all following analyses to the passed or newly created group
Expand Down Expand Up @@ -308,7 +308,7 @@ def analyze(
return

issues_list: List[
Tuple[DetectedIssuesResponse, Optional[AnalysisInputResponse]]
Tuple[str, DetectedIssuesResponse, Optional[AnalysisInputResponse]]
] = []
formatter: BaseFormatter = FORMAT_RESOLVER[ctx["fmt"]]
for uuid in uuids:
Expand All @@ -331,8 +331,8 @@ def analyze(
swc_whitelist=swc_whitelist,
)
# extend response with job UUID to keep formatter logic isolated
resp.uuid = uuid
issues_list.append((resp, inp))
# resp.uuid = uuid
issues_list.append((uuid, resp, inp))

LOGGER.debug(
f"Printing report for {len(issues_list)} issue items with sort key \"{ctx['table_sort_key']}\""
Expand Down
20 changes: 20 additions & 0 deletions mythx_cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from mythx_cli.group.list import group_list
from mythx_cli.group.open import group_open
from mythx_cli.group.status import group_status
from mythx_cli.project.list import project_list
from mythx_cli.render.command import render
from mythx_cli.util import update_context
from mythx_cli.version.command import version
Expand Down Expand Up @@ -205,6 +206,25 @@ def cli(
cli.add_command(version)


@cli.group()
def project() -> None:
"""Create, modify, and view analysis projects.

\f

This subcommand holds all project-related actions, such as creating,
listing, and managing projects, as well as fetching the status of one
or more groups inside a project.
"""
pass


from mythx_cli.project import project_list

LOGGER.debug("Registering project commands")
project.add_command(project_list)


@cli.group()
def group() -> None:
"""Create, modify, and view analysis groups.
Expand Down
2 changes: 1 addition & 1 deletion mythx_cli/formatter/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def format_analysis_status(resp: AnalysisStatusResponse) -> str:
@abc.abstractmethod
def format_detected_issues(
issues_list: List[
Tuple[DetectedIssuesResponse, Optional[AnalysisInputResponse]]
Tuple[str, DetectedIssuesResponse, Optional[AnalysisInputResponse]]
],
**kwargs,
) -> str:
Expand Down
33 changes: 23 additions & 10 deletions mythx_cli/formatter/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
DetectedIssuesResponse,
GroupListResponse,
GroupStatusResponse,
ProjectListResponse,
VersionResponse,
)

Expand All @@ -29,43 +30,49 @@ class JSONFormatter(BaseFormatter):
def format_group_status(resp: GroupStatusResponse) -> str:
"""Format a group status response as compressed JSON."""

return resp.to_json()
return resp.json(by_alias=True)

@staticmethod
def format_project_list(resp: ProjectListResponse):
"""Format a project list response as pretty-printed JSON."""

return resp.json(by_alias=True)

@staticmethod
def format_group_list(resp: GroupListResponse) -> str:
"""Format a group list response as compressed JSON."""

return resp.to_json()
return resp.json(by_alias=True)

@staticmethod
def format_analysis_list(resp: AnalysisListResponse) -> str:
"""Format an analysis list response as compressed JSON."""

return resp.to_json()
return resp.json(by_alias=True)

@staticmethod
def format_analysis_status(resp: AnalysisStatusResponse) -> str:
"""Format an analysis status response as compressed JSON."""

return resp.to_json()
return resp.json(by_alias=True)

@staticmethod
def format_detected_issues(
issues_list: List[
Tuple[DetectedIssuesResponse, Optional[AnalysisInputResponse]]
Tuple[str, DetectedIssuesResponse, Optional[AnalysisInputResponse]]
],
**kwargs,
) -> str:
"""Format an issue report response as compressed JSON."""

output = [resp.to_dict(as_list=True) for resp, _ in issues_list]
output = [resp.dict(by_alias=True) for _, resp, _ in issues_list]
return json.dumps(output)

@staticmethod
def format_version(resp: VersionResponse) -> str:
"""Format a version response as compressed JSON."""

return resp.to_json()
return resp.json(by_alias=True)


class PrettyJSONFormatter(BaseFormatter):
Expand All @@ -85,9 +92,15 @@ def _print_as_json(obj, report_mode=False) -> str:
json_args = {"indent": 2, "sort_keys": True}
if report_mode:
return json.dumps(
[resp.to_dict(as_list=True) for resp, _ in obj], **json_args
[resp.dict(by_alias=True) for _, resp, _ in obj], **json_args
)
return json.dumps(obj.to_dict(), **json_args)
return json.dumps(obj.dict(by_alias=True), **json_args)

@staticmethod
def format_project_list(resp: ProjectListResponse):
"""Format a project list response as pretty-printed JSON."""

return PrettyJSONFormatter._print_as_json(resp)

@staticmethod
def format_group_status(resp: GroupStatusResponse) -> str:
Expand Down Expand Up @@ -116,7 +129,7 @@ def format_analysis_status(obj: AnalysisStatusResponse) -> str:
@staticmethod
def format_detected_issues(
issues_list: List[
Tuple[DetectedIssuesResponse, Optional[AnalysisInputResponse]]
Tuple[str, DetectedIssuesResponse, Optional[AnalysisInputResponse]]
],
**kwargs,
) -> str:
Expand Down
40 changes: 28 additions & 12 deletions mythx_cli/formatter/simple_stdout.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
DetectedIssuesResponse,
GroupListResponse,
GroupStatusResponse,
ProjectListResponse,
VersionResponse,
)

Expand All @@ -33,7 +34,7 @@ def format_analysis_list(resp: AnalysisListResponse) -> str:
"""Format an analysis list response to a simple text representation."""

res = []
for analysis in resp:
for analysis in resp.analyses:
res.append("UUID: {}".format(analysis.uuid))
res.append("Submitted at: {}".format(analysis.submitted_at))
res.append("Status: {}".format(analysis.status))
Expand All @@ -46,10 +47,10 @@ def format_group_status(resp: GroupStatusResponse) -> str:
"""Format a group status response to a simple text representation."""

res = [
"ID: {}".format(resp.group.identifier),
"Name: {}".format(resp.group.name or "<unnamed>"),
"Created on: {}".format(resp.group.created_at),
"Status: {}".format(resp.group.status),
"ID: {}".format(resp.identifier),
"Name: {}".format(resp.name or "<unnamed>"),
"Created on: {}".format(resp.created_at),
"Status: {}".format(resp.status),
"",
]
return "\n".join(res)
Expand All @@ -60,7 +61,7 @@ def format_group_list(resp: GroupListResponse) -> str:
representation."""

res = []
for group in resp:
for group in resp.groups:
res.append("ID: {}".format(group.identifier))
res.append("Name: {}".format(group.name or "<unnamed>"))
res.append("Created on: {}".format(group.created_at))
Expand All @@ -69,6 +70,21 @@ def format_group_list(resp: GroupListResponse) -> str:

return "\n".join(res)

@staticmethod
def format_project_list(resp: ProjectListResponse) -> str:
"""Format an analysis group response to a simple text
representation."""

res = []
for project in resp.projects:
res.append("ID: {}".format(project.id))
res.append("Name: {}".format(project.name or "<unnamed>"))
res.append("Created on: {}".format(project.created))
res.append("Modified: {}".format(project.modified))
res.append("")

return "\n".join(res)

@staticmethod
def format_analysis_status(resp: AnalysisStatusResponse) -> str:
"""Format an analysis status response to a simple text
Expand All @@ -85,7 +101,7 @@ def format_analysis_status(resp: AnalysisStatusResponse) -> str:
@staticmethod
def format_detected_issues(
issues_list: List[
Tuple[DetectedIssuesResponse, Optional[AnalysisInputResponse]]
Tuple[str, DetectedIssuesResponse, Optional[AnalysisInputResponse]]
],
**kwargs,
) -> str:
Expand Down Expand Up @@ -113,10 +129,10 @@ def format_version(resp: VersionResponse) -> str:

return "\n".join(
[
"API: {}".format(resp.api_version),
"Harvey: {}".format(resp.harvey_version),
"Maru: {}".format(resp.maru_version),
"Mythril: {}".format(resp.mythril_version),
"Hashed: {}".format(resp.hashed_version),
"API: {}".format(resp.api),
"Harvey: {}".format(resp.harvey),
"Maru: {}".format(resp.maru),
"Mythril: {}".format(resp.mythril),
"Hashed: {}".format(resp.hash),
]
)
Loading