forked from infra/ansible
feat(openshift-app-image-report): add enviroment searching
Signed-off-by: Vít Smolík <me@smoliicek.cz>
This commit is contained in:
parent
412e39286b
commit
383ec9d082
1 changed files with 101 additions and 9 deletions
|
|
@ -46,6 +46,7 @@ IMAGE_PREFIXES = (
|
|||
@dataclass(frozen=True)
|
||||
class Finding:
|
||||
app: str
|
||||
env: str
|
||||
kind: str
|
||||
image: str
|
||||
os_version: str
|
||||
|
|
@ -79,6 +80,7 @@ def _looks_like_image(value):
|
|||
def _tag_from_image(image):
|
||||
if "@" in image:
|
||||
return image.rsplit("@", 1)[1]
|
||||
# Only inspect the image-name tail so registry ports are not treated as tags.
|
||||
tail = image.rsplit("/", 1)[-1]
|
||||
if ":" not in tail:
|
||||
return ""
|
||||
|
|
@ -128,7 +130,7 @@ def _infer_os_version(image):
|
|||
return "busybox"
|
||||
if low.startswith("redis:"):
|
||||
return "redis"
|
||||
if "bitnami" in low and "redis" in low:
|
||||
if "bitnami" in low and re.search(r"(^|[/:_-])redis($|[/:_.-])", low):
|
||||
return "redis"
|
||||
if low.startswith("solr:"):
|
||||
return "Solr"
|
||||
|
|
@ -157,6 +159,64 @@ def _source_kind(line, value):
|
|||
return "image-reference"
|
||||
|
||||
|
||||
def _env_from_jinja_condition(line):
|
||||
if "env" not in line:
|
||||
return None
|
||||
if re.search(r"env\s*==\s*['\"]staging['\"]", line):
|
||||
return "staging"
|
||||
if re.search(r"env\s*==\s*['\"]production['\"]", line):
|
||||
return "production"
|
||||
return "conditional"
|
||||
|
||||
|
||||
def _opposite_env(env):
|
||||
if env == "staging":
|
||||
return "production"
|
||||
if env == "production":
|
||||
return "staging"
|
||||
return "conditional"
|
||||
|
||||
|
||||
def _update_env_scope(line, env_stack, current_env):
|
||||
stripped = line.strip()
|
||||
if not stripped.startswith("{%"):
|
||||
return current_env
|
||||
|
||||
if re.match(r"{%-?\s*if\b", stripped):
|
||||
next_env = _env_from_jinja_condition(stripped)
|
||||
env_stack.append((current_env, next_env))
|
||||
return next_env or current_env
|
||||
|
||||
if re.match(r"{%-?\s*elif\b", stripped):
|
||||
next_env = _env_from_jinja_condition(stripped)
|
||||
if env_stack:
|
||||
previous_env, _old_env = env_stack[-1]
|
||||
env_stack[-1] = (previous_env, next_env)
|
||||
return next_env or current_env
|
||||
|
||||
if re.match(r"{%-?\s*else\s*-?%}", stripped):
|
||||
if not env_stack:
|
||||
return "conditional"
|
||||
_previous_env, if_env = env_stack[-1]
|
||||
if if_env is None:
|
||||
return current_env
|
||||
return _opposite_env(if_env)
|
||||
|
||||
if re.match(r"{%-?\s*endif\s*-?%}", stripped):
|
||||
if not env_stack:
|
||||
return "all"
|
||||
previous_env, _if_env = env_stack.pop()
|
||||
return previous_env
|
||||
|
||||
return current_env
|
||||
|
||||
|
||||
def _line_env(line, current_env):
|
||||
if "{{" in line and "env" in line and "ternary" in line:
|
||||
return "conditional"
|
||||
return current_env
|
||||
|
||||
|
||||
def _iter_scan_files(app_dir):
|
||||
for path in sorted(app_dir.rglob("*")):
|
||||
if not path.is_file():
|
||||
|
|
@ -179,7 +239,10 @@ def _scan_line(line):
|
|||
return None
|
||||
|
||||
if stripped.startswith("FROM "):
|
||||
return stripped.split(None, 1)[1].split()[0], True
|
||||
parts = stripped.split()
|
||||
if len(parts) > 1:
|
||||
return parts[1], True
|
||||
return None
|
||||
|
||||
for key in ("image:", "name:", "dockerfilePath:", "uri:", "ref:"):
|
||||
if not stripped.startswith(key):
|
||||
|
|
@ -215,7 +278,10 @@ def scan_apps(app_filter=None):
|
|||
lines = path.read_text(encoding="utf-8").splitlines()
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
env_stack = []
|
||||
current_env = "all"
|
||||
for lineno, line in enumerate(lines, 1):
|
||||
current_env = _update_env_scope(line, env_stack, current_env)
|
||||
scanned = _scan_line(line)
|
||||
if not scanned:
|
||||
continue
|
||||
|
|
@ -228,6 +294,7 @@ def scan_apps(app_filter=None):
|
|||
findings.append(
|
||||
Finding(
|
||||
app=app,
|
||||
env=_line_env(line, current_env),
|
||||
kind=kind,
|
||||
image=value,
|
||||
os_version=os_version,
|
||||
|
|
@ -294,6 +361,7 @@ def apply_annotations(findings, annotations):
|
|||
updated.append(
|
||||
Finding(
|
||||
app=finding.app,
|
||||
env=finding.env,
|
||||
kind=kind,
|
||||
image=finding.image,
|
||||
os_version=os_version,
|
||||
|
|
@ -309,6 +377,12 @@ def apply_annotations(findings, annotations):
|
|||
|
||||
def filter_findings(findings, args):
|
||||
filtered = findings
|
||||
if args.env:
|
||||
wanted = set(args.env)
|
||||
env_matches = set(wanted)
|
||||
if "staging" in wanted or "production" in wanted:
|
||||
env_matches.add("all")
|
||||
filtered = [item for item in filtered if item.env in env_matches]
|
||||
if args.kind:
|
||||
wanted = set(args.kind)
|
||||
filtered = [item for item in filtered if item.kind in wanted]
|
||||
|
|
@ -338,34 +412,43 @@ def _clip(value, width):
|
|||
def print_table(findings):
|
||||
columns = (
|
||||
("APP", 22),
|
||||
("ENV", 11),
|
||||
("KIND", 16),
|
||||
("OS/VERSION", 20),
|
||||
("FLOAT", 5),
|
||||
("IMAGE/SOURCE", 54),
|
||||
("PATH", 58),
|
||||
("NOTE", 30),
|
||||
("IMAGE/SOURCE", None),
|
||||
)
|
||||
rows = []
|
||||
for finding in findings:
|
||||
rows.append(
|
||||
(
|
||||
finding.app,
|
||||
finding.env,
|
||||
finding.kind,
|
||||
finding.os_version,
|
||||
"yes" if finding.floating else "",
|
||||
("?" if finding.unresolved else "") + finding.image,
|
||||
_short_path(finding),
|
||||
finding.note,
|
||||
("?" if finding.unresolved else "") + finding.image,
|
||||
)
|
||||
)
|
||||
|
||||
header = " ".join(name.ljust(width) for name, width in columns)
|
||||
print(header)
|
||||
print(" ".join("-" * width for _name, width in columns))
|
||||
print(
|
||||
" ".join(
|
||||
name if width is None else name.ljust(width) for name, width in columns
|
||||
)
|
||||
)
|
||||
print(
|
||||
" ".join(
|
||||
"-" * len(name) if width is None else "-" * width for name, width in columns
|
||||
)
|
||||
)
|
||||
for row in rows:
|
||||
print(
|
||||
" ".join(
|
||||
_clip(value, width).ljust(width)
|
||||
value if width is None else _clip(value, width).ljust(width)
|
||||
for value, (_name, width) in zip(row, columns)
|
||||
)
|
||||
)
|
||||
|
|
@ -386,6 +469,15 @@ def parse_args(argv):
|
|||
action="append",
|
||||
help="Only report one finding kind. May be used more than once.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--env",
|
||||
action="append",
|
||||
choices=("all", "staging", "production", "conditional"),
|
||||
help=(
|
||||
"Only report one environment scope. May be used more than once. "
|
||||
"staging and production also include all."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--floating-only",
|
||||
action="store_true",
|
||||
|
|
@ -414,7 +506,7 @@ def main(argv=None):
|
|||
findings, unused_annotations = apply_annotations(findings, load_annotations(notes_file))
|
||||
findings = filter_findings(findings, args)
|
||||
findings = sorted(
|
||||
findings, key=lambda item: (item.app, item.path, item.line, item.image)
|
||||
findings, key=lambda item: (item.app, item.path, item.line, item.env, item.image)
|
||||
)
|
||||
if unused_annotations and not args.no_note_warnings:
|
||||
for app, match in unused_annotations:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue