Add check command

This commit is contained in:
Jacob Hinkle 2022-10-12 16:36:58 -04:00
parent 3beff36d66
commit b6a781e386
3 changed files with 154 additions and 3 deletions

View File

@ -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
View 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,
)

View File

@ -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