Add check command
This commit is contained in:
parent
3beff36d66
commit
b6a781e386
@ -3,9 +3,7 @@ from loguru import logger
|
|||||||
|
|
||||||
from ..version import __version__
|
from ..version import __version__
|
||||||
|
|
||||||
from . import diff
|
from . import check, diff, record, show
|
||||||
from . import record
|
|
||||||
from . import show
|
|
||||||
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
@ -50,6 +48,7 @@ def main(log_level: str) -> None:
|
|||||||
logger.add(sys.stderr, level=log_level)
|
logger.add(sys.stderr, level=log_level)
|
||||||
|
|
||||||
|
|
||||||
|
main.add_command(check.check_cli, name="check")
|
||||||
main.add_command(diff.status, name="status")
|
main.add_command(diff.status, name="status")
|
||||||
main.add_command(record.record_cli, name="record")
|
main.add_command(record.record_cli, name="record")
|
||||||
main.add_command(show.show_cli, name="show")
|
main.add_command(show.show_cli, name="show")
|
||||||
|
|||||||
143
src/nancy/cli/check.py
Normal file
143
src/nancy/cli/check.py
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
import click
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
from .. import fs, store
|
||||||
|
from .diff import print_diff
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from typing import Any, List, Optional, Union
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
|
||||||
|
@logger.catch
|
||||||
|
def check(
|
||||||
|
store_path: Optional[Union[str, "os.PathLike[Any]"]] = None,
|
||||||
|
show_hashes: bool = False,
|
||||||
|
use_color: bool = True,
|
||||||
|
) -> None:
|
||||||
|
"""Unwrapped check command"""
|
||||||
|
start_time = datetime.now()
|
||||||
|
|
||||||
|
if store_path is None:
|
||||||
|
curdir = os.path.realpath(os.getcwd())
|
||||||
|
logger.info("Looking for store in {}", curdir)
|
||||||
|
store_path = store.find_store(curdir)
|
||||||
|
if store_path is None: # If no store found, assume we're creating here
|
||||||
|
logger.error(
|
||||||
|
"Could not find nancy.db in any directory containing {}", curdir
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
|
logger.info("Found store at {}", store_path)
|
||||||
|
s = store.Store(store_path)
|
||||||
|
|
||||||
|
cur = s.conn.cursor()
|
||||||
|
|
||||||
|
def columns(table: str, columns: List[str]):
|
||||||
|
cur.execute(f"SELECT {','.join(columns)} FROM {table}")
|
||||||
|
return cur.fetchall()
|
||||||
|
|
||||||
|
def oneresult(qry: str):
|
||||||
|
"""Run a query that returns just one thing"""
|
||||||
|
cur.execute(qry)
|
||||||
|
(c,) = cur.fetchone()
|
||||||
|
return c
|
||||||
|
|
||||||
|
def count(table: str):
|
||||||
|
return oneresult(f"SELECT COUNT(*) FROM {table}")
|
||||||
|
|
||||||
|
local_uuid = oneresult('SELECT value FROM local_metadata WHERE key == "store_uuid"')
|
||||||
|
print(f'Local store has UUID "{local_uuid}"')
|
||||||
|
|
||||||
|
print(f"Stores ({count('store')}):")
|
||||||
|
for (storeid,) in columns("store", ["uuid"]):
|
||||||
|
print(f" {storeid}")
|
||||||
|
|
||||||
|
print(f"Machines ({count('machine')}):")
|
||||||
|
for (sha256, name, machineid) in columns(
|
||||||
|
"machine", ["sha256", "hostname", "machine_id"]
|
||||||
|
):
|
||||||
|
print(f" {name:15} (sha256: {sha256}, machine-id: {machineid})")
|
||||||
|
|
||||||
|
print(f"Users ({count('user')}):")
|
||||||
|
cur.execute("SELECT user.sha256,fullname,username,hostname FROM user JOIN machine")
|
||||||
|
for sha256, full, uname, hostname in cur.fetchall():
|
||||||
|
print(f' {uname + "@" + hostname:20} (fullname: {full}, sha256: {sha256}')
|
||||||
|
|
||||||
|
print(f"Programs ({count('program')}):")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT program.uuid,start_time,end_time,username,hostname,name,message
|
||||||
|
FROM program
|
||||||
|
JOIN environment JOIN user JOIN machine
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
for progid, starttime, endtime, uname, host, progname, message in cur.fetchall():
|
||||||
|
starttime = datetime.fromtimestamp(starttime)
|
||||||
|
endtime = datetime.fromtimestamp(endtime)
|
||||||
|
print(
|
||||||
|
f" {progname} "
|
||||||
|
f"(message:{message}, "
|
||||||
|
f"user:{uname}@{host}, "
|
||||||
|
f"start: {starttime} {starttime.tzinfo}, "
|
||||||
|
f"elapsed: {endtime - starttime})"
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"Environments: {count('environment')}")
|
||||||
|
print(f"Packages: {count('package')}")
|
||||||
|
print(f"Modules: {count('module')}")
|
||||||
|
print(f"Functions: {count('func')}")
|
||||||
|
print(f"Tasks: {count('task')}")
|
||||||
|
print(f"Files/Directories: {count('filedir')}")
|
||||||
|
print(f"File/Directory versions: {count('filedir_version')}")
|
||||||
|
|
||||||
|
# then find a listing covering all the expected paths
|
||||||
|
print("Checking for changes to filesystem. This may take a while...")
|
||||||
|
diff = s.diff()
|
||||||
|
if diff.A is None:
|
||||||
|
logger.warning(
|
||||||
|
f"No recorded entries in {store_path}. Have you run `nancy record` yet?"
|
||||||
|
)
|
||||||
|
|
||||||
|
if diff.is_modified():
|
||||||
|
logger.warning("Files have been modified:")
|
||||||
|
print_diff(diff)
|
||||||
|
else:
|
||||||
|
print("All files/directories are consistent with recorded versions.")
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.option(
|
||||||
|
"-H",
|
||||||
|
"--show-hashes",
|
||||||
|
is_flag=True,
|
||||||
|
help="If given, prepend each line in the diff with the new file hash (SHA256).",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--no-color",
|
||||||
|
is_flag=True,
|
||||||
|
help="If given, do not print any color output.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"-s",
|
||||||
|
"--store",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help="Top-level of store. If omitted, use closest common parent directory "
|
||||||
|
"of given paths. If given the path to a non-store directory, a new "
|
||||||
|
"store is initialized there.",
|
||||||
|
)
|
||||||
|
def check_cli(
|
||||||
|
show_hashes: bool,
|
||||||
|
no_color: bool,
|
||||||
|
store: str,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Check the store for altered files and other problems.
|
||||||
|
"""
|
||||||
|
check(
|
||||||
|
show_hashes=show_hashes,
|
||||||
|
use_color=not no_color,
|
||||||
|
store_path=store,
|
||||||
|
)
|
||||||
@ -432,6 +432,15 @@ class FSDiff:
|
|||||||
and Alatest.deleted == Blatest.deleted
|
and Alatest.deleted == Blatest.deleted
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def is_modified(self):
|
||||||
|
"""Return True only if there is a difference between A and B."""
|
||||||
|
if len(self.modified_children) > 0:
|
||||||
|
return True
|
||||||
|
if self.A is None or self.B is None:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return not self.__class__.compare(self.A, self.B)
|
||||||
|
|
||||||
def filename(self) -> str:
|
def filename(self) -> str:
|
||||||
if self.A is not None:
|
if self.A is not None:
|
||||||
return self.A.filename
|
return self.A.filename
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user